چی به Laravel 13.26 اضافه شده؟
حدود ۷ دقیقه مطالعه
هجدهم آگوست، Laravel 13.26 منتشر شد. تمرکز اصلی این نسخه رو دیسکهای read-through برای مهاجرت تنبل بین storage ها، debounce کردن listener های صف، و یه راه ساده برای reroute کردن کل یه صف بدون دست زدن به کد job ها گذاشته.
دیسکهای Read-Through: مهاجرت تنبل بین storage ها
یه درایور جدید به اسم read-through دو تا دیسک موجود رو با هم ترکیب میکنه. خوندن ابتدا از دیسک اصلی چک میشه و اگه پیدا نشد، به دیسک fallback برمیگرده؛ در همون لحظه، فایل رو هم رو دیسک اصلی کپی میکنه (promote). یعنی دیسک اصلی بهمرور و فقط با فایلهایی که واقعاً درخواست شدن پر میشه:
'assets' => [
'driver' => 'primary',
'primary' => 'r2',
'fallback' => 'legacy-s3',
],
نوشتن، حذف، و لیست کردن پوشهها فقط رو دیسک اصلی انجام میشن. یه گزینهی copy => false هم هست که فایل رو از fallback سرو میکنه بدون اینکه چیزی رو promote کنه؛ مناسب محیطهای development که به فایلهای production اشاره میکنن. اگه promotion به هر دلیلی fail بشه، بهصورت پیشفرض این خطا بلعیده میشه تا یه خوندن ساده خراب نشه؛ برای دیدن این خطاها، گزینهی throw_on_promotion_failure => true رو فعال کن.
این قابلیت دقیقاً برای سناریوهایی طراحی شده که میخوای از یه storage قدیمی به یه storage جدید (مثلاً از S3 به Cloudflare R2) کوچ کنی، بدون اینکه مجبور بشی همهی فایلها رو یکجا منتقل کنی.
Debounce کردن Listener های صف
Attribute ای به اسم #[DebounceFor] که تو Laravel 13.6 برای job های صف معرفی شده بود، حالا رو listener های event صف هم کار میکنه. وقتی یه event پشتسرهم چند بار fire بشه، فقط آخرین dispatch تو همون بازهی زمانی، واقعاً listener رو اجرا میکنه:
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\Attributes\DebounceFor;
#[DebounceFor(30, maxWait: 120)]
class UpdateProductSearchIndex implements ShouldQueue
{
public function debounceId(ProductUpdated $event): string
{
return (string) $event->product->getKey();
}
public function handle(ProductUpdated $event): void
{
// فقط یه بار، با آخرین وضعیت، ایندکس رو آپدیت میکنه
}
}
متد debounceId() مشخص میکنه این بازهی زمانی برای کدوم resource حساب بشه، و maxWait سقف میذاره که اگه event خیلی زیاد و پشتسرهم fire بشه، اجرای کار برای همیشه عقب نیفته. این debounce فقط رو همون listener اعمال میشه؛ بقیهی listener های همون event همچنان هر بار اجرا میشن. یه نکتهی مهم: یه listener که #[DebounceFor] داره، نمیتونه همزمان ShouldBeUnique رو implement کنه؛ dispatcher یه LogicException پرتاب میکنه، چون uniqueness اولین dispatch رو نگه میداره ولی debounce آخرین رو.
Queue::forward(): مسیردهی مجدد صف از یه نقطه
این متد جدید همهچیزی که به یه صف dispatch میشه رو به یه صف دیگه، یه connection دیگه، یا هر دو، منتقل میکنه؛ همهی اینها از یه نقطهی مرکزی تو service provider، بدون نیاز به تغییر کلاسهای job یا محلهای dispatch:
use Illuminate\Support\Facades\Queue;
Queue::forward('reports', 'reports.fifo', 'cloud'); // تغییر اسم و جابهجایی connection
Queue::forward('payments', connection: 'cloud'); // فقط جابهجایی connection
Queue::forward('updates', 'notifications'); // تغییر اسم رو همون connection
Queue::forward([
'reports' => 'reports.fifo',
'emails' => 'emails.fifo',
], connection: 'cloud');
این forward ها از همون hook ای که Queue::route() استفاده میکنه عبور میکنن، پس مهم نیست job از چه مسیری به صف میرسه؛ همیشه اعمال میشه.
بهبودهای Process
سه تا اضافهی مربوط به facade Process:
Process pool های iterable: قبلاً foreach روی ProcessPoolResults عملاً هیچی رو iterate نمیکرد (چون این کلاس هیچ پراپرتی public ای نداشت)، یعنی حلقهای که برای چک کردن exit code نوشته میشد، بیصدا هیچی رو چک نمیکرد. حالا هر دو کلاس IteratorAggregate رو implement میکنن.
Exception جدا برای idle timeout: یه idle timeout حالا ProcessIdleTimedOutException خودش رو پرتاب میکنه، جدا از ProcessTimedOutException:
try {
Process::timeout(600)->idleTimeout(30)->run('./deploy.sh');
} catch (ProcessIdleTimedOutException $e) {
// ۳۰ ثانیه هیچ خروجی نداشته: kill کن و هشدار بده
} catch (ProcessTimedOutException $e) {
// هنوز خروجی میده، فقط کنده: با زمان بیشتر دوباره امتحان کن
}
این تفکیک مهمه چون این دو حالت معنی کاملاً متفاوتی دارن: یکی یه پروسهی گیرکردهست، اون یکی فقط کنده.
Assertion های جدید برای Process fake: assertRanCount(2)، assertRanInOrder([...])، و متد recorded() برای فیلتر کردن جفتهای پروسه/نتیجهی ثبتشده با یه callback.
اضافات Eloquent Builder
orWhereKey()وorWhereKeyNot()بهwhereKey()اضافه شدن؛ جایگزین بهتری برایorWhereIn('id', ...)تو کوئریهایی که نیاز به شرط primary key تو یه شاخهیorدارن.wherePivot()وorWherePivot()حالا یه closure قبول میکنن که یه builder scopeشده به مدل pivot بهش پاس میشه؛ یعنی scope های تعریفشده رو یه کلاس pivot سفارشی، مستقیم از کوئری رابطه قابلفراخوانی میشن.inOrderOf()حالا enum ها رو تو لیست مقادیرش قبول میکنه.
$project->subscribers()
->wherePivot(fn ($query) => $query->active()->notMuted())
->get();
دیدهشدن بهتر Queue Worker
یه event جدید به اسم JobReleased حالا موقعی که یه job از handler خودش یا از middleware ای مثل WithoutOverlapping به صف برگردونده میشه، fire میشه. تا قبل از این، JobReleasedAfterException فقط release هایی که بهخاطر throw شدن یه exception اتفاق میافتادن رو پوشش میداد؛ یعنی release ناشی از overlap کاملاً نامرئی بود.
Worker ها هم حالا وقتی صفی که ازش مصرف میکنن pause میشه، یه پیام تو خروجی چاپ میکنن، و همینطور وقتی resume میشه. از وقتی API پاز صف تو Laravel 13.25 اومد، یه worker پازشده فقط تولید خروجی رو متوقف میکرد و شبیه یه hang به نظر میرسید؛ این آپدیت این ابهام رو برطرف میکنه.
بقیهی رفع باگها و بهبودها
- پشتیبانی از Guzzle 8 در کنار Guzzle 7.
- دستور
db:seed --class=SpecificSeederحالا همون خروجی پیشرفت RUNNING/DONE رو نشون میده که seeder های تودرتو از قبل داشتن. - یه دسته بهبود امنیت/پایداری Redis: چکهای صف pauseشده دیگه رو کلاستر cross-slot read انجام نمیدن، pruning تگهای کش همهی master node ها رو اسکن میکنه و دیگه رو تگهای قدیمی loop بینهایت نمیزنه، یه pipeline یا transaction failشده اتصال رو قابلاستفاده نگه میداره.
- درایور صف Cloud متد
managedQueues()رو expose میکنه، و long-poll های timeoutشدهی cloud agent حالا retry میشن. throwUnless()دیگه تو یه حالت خاص که باید throw میکرد، بیصدا هیچ کاری نمیکنه.- تنظیم
readonly=falseتو Microsoft SQL Server رعایت میشه، و quote های تکی تو attribute های JSON path پستگرس escape میشن.
نکات آپگرید
طبق اعلام رسمی، هیچ breaking change ای برای اپلیکیشنهای معمولی انتظار نمیره. فقط دو نکتهی ریز رو باید بدونی: یه listener که #[DebounceFor] داره، اگه همزمان ShouldBeUnique رو هم implement کنه، حالا یه LogicException میگیره؛ و internal های whereKey()/whereKeyNot() ادغام شدن، که فقط اگه Eloquent builder رو extend کرده باشی و این متدها رو override کرده باشی برات مهمه.
جمعبندی
اگه بخوایم Laravel 13.26 رو خلاصه کنیم: تمرکز اصلی رو مهاجرت تنبل بین storage ها (read-through disks)، کنترل بهتر روی صفها (debounce برای listener، forward برای مسیردهی مجدد، دیدهشدن بهتر worker)، و صیقل دادن جزئیات Process و Eloquent builder گذاشته. مثل چند نسخهی اخیر، آپگرید نسبتاً بیدردسره و میشه بدون نگرانی زیاد بهش رفت.
