با Expressive، منطق کسبوکار رو از Eloquent جدا کن
خلاصهٔ کاملتر
نویسنده میگه Eloquent یکی از بهترین بخشهای لاراوله و کار با کوئری، رابطهها، کستها و رویدادها رو لذتبخش میکنه. ولی هرچی اپلیکیشن بزرگتر میشه یه مشکل آشنا سر و کلهش پیدا میشه: مدل Eloquent میشه آبجکتی که همهجا پاس داده میشه؛ کنترلرها، اکشنها، سرویسها، جابها و پالیسیها همه مدل میگیرن و کلی منطق کسبوکار مستقیم به آبجکتی وابسته میشه که بلده با دیتابیس هم حرف بزنه.
به گفتهٔ نویسنده این همیشه فاجعه نیست و برای خیلی از فلوهای ساده کاملاً اوکیه، ولی برای فلوهای مهمتر باعث میشه فهمِ اپلیکیشن سختتر شه؛ چون یه مدل فقط دادهٔ خالی نیست، بلکه همزمان نقطهٔ ورود کوئری، لایهٔ ماندگاری، مدیر رابطهها، منبع رویداد و جای اسکوپها و اکسسورهاست. وقتی منطق کسبوکار یه مدل میگیره، خیلی بیشتر از دادهٔ موردنیازش رو دریافت میکنه.
نویسنده تأکید میکنه Expressive جایگزین Eloquent نیست. Eloquent هنوز صاحب کوئری، رابطهها، کستها و نوشتن تو دیتابیسه؛ Expressive فقط یه مرز تایپشده روش میذاره. ایده سادهست: با مدلها با دیتابیس حرف بزن، وقتی منطق برنامه دادهٔ تایپشده لازم داره اونها رو به آبجکت Expressive تبدیل کن، منطق رو به این آبجکتها وابسته کن و برای ذخیره دوباره به Eloquent برگرد.
یه آبجکت Expressive فقط یه کلاس با پراپرتیهای عمومیِ تایپشدهست؛ مثلاً customerId یه عدد صحیحه، status یه enum از نوع OrderStatus و paidAt نالپذیره. مدل هم با تریت IsExpressive به این قابلیت opt-in میکنه و بعد با فراخوانی متد expressive() میتونی مدل، کالکشن یا حتی بیلدر رو به آبجکتهای تایپشده تبدیل کنی. یه نکتهٔ خوبش اینه که پراپرتیهای مجازی خودکار append نمیشن؛ باید صریح درخواستشون کنی.
نویسنده مثال یه اکشن پرداخت رو بازنویسی میکنه تا فرق رو نشون بده:
public function handle(Order $order, PaymentGateway $gateway): PaymentResult
{
if ($order->status !== OrderStatus::Pending) {
return PaymentResult::rejected('The order is not pending.');
}
$gateway->charge(
customerId: $order->customerId,
amountInCents: $order->totalCents,
);
$order->status = OrderStatus::Paid;
return PaymentResult::paid($order);
}حالا این اکشن فقط با دادهای که لازم داره کار میکنه، نه با کل آبجکت دیتابیس. نمیتونه اشتباهی delete() رو صدا بزنه یا از روی آبجکت کوئری بسازه، و اسمهای دیتابیسی مثل customer_id و total_cents رو هم نمیشناسه؛ فقط شکل سطحبرنامه یعنی customerId و totalCents رو میبینه. این همون سود جداسازیه.
نویسنده جمعبندی میکنه که این یه معماری کاملاً متفاوت نیست، فقط یه مرز کوچیکه که بقیهٔ کد رو صریحتر میکنه؛ دیتابیس هنوز سر جاشه و ما فقط مرز رو جابهجا کردیم تا فلوی کسبوکار بهجای وابستگی مستقیم به Eloquent، به یه آبجکت تایپشده وابسته باشه. برای مجموعههای بزرگتر هم متد expressiveChunk() هست تا بدون بارگذاری همهچیز تو حافظه، آبجکتهای تایپشده رو پردازش کنی.
نکات کلیدی:
- مشکل: پاس دادن مدل Eloquent به همهجا، منطق کسبوکار رو به لایهٔ دیتابیس گره میزنه
- Expressive یه آبجکت با پراپرتیهای تایپشده روی Eloquent میسازه
- مدل با تریت IsExpressive و متد expressive() تبدیل میشه
- اکشن دیگه نمیتونه کوئری بسازه یا رکورد رو حذف کنه و فقط دادهٔ لازم رو میبینه
- Expressive جایگزین Eloquent نیست؛ فقط یه مرز تایپشده روش میذاره




