Laravel 13.39: أمر session:garbage-collect وموديلات بلا مفتاح أساسي
جديد Laravel 13.39: أمر session:garbage-collect لتنظيف الجلسات بالجدولة، وموديلات بلا مفتاح أساسي، وكتابة آمنة لملفات الكاش. إليك ما تغيّر وكيف تترقّى.
صدر الإصدار في 27 أكتوبر 2026. إذا كان تطبيقك يستخدم جلسات في قاعدة البيانات، فهناك عمل خفي يُنفَّذ أثناء طلبات المستخدمين لتنظيف الجلسات القديمة. هذا الإصدار يمنحك طريقة لنقل ذلك العمل خارج مسار الطلب. وفيه أيضًا تغييرات صغيرة لكنها تمسّ الإنتاج مباشرة: ملف كاش فارغ قد يوقف تطبيقك، ومهمة فاشلة بلا بيانات توقيت، وموديل لا يملك عمود id. نمرّ هنا على أهم ما في الإصدار، ثم على التحفظات قبل الترقية.
أمر session:garbage-collect: تنظيف الجلسات بالجدولة
بحسب شرح Laravel News، يعتمد Laravel افتراضيًا على ما يسمى "lottery" في ملف config/session.php: في بعض الطلبات العشوائية يُشغَّل منظّف الجلسات المنتهية. معنى ذلك أن مستخدمًا عاديًا قد يتحمل زمن التنظيف داخل طلبه دون أن يعلم.
الإضافة الجديدة، من مساهمة Andrew Brown (PR رقم 62104، وسُجّل الأمر في PR رقم 62150)، هي أمر Artisan يشغّل المنظّف الخاص بالـ session driver مباشرة، فتجدوله كأي مهمة أخرى:
use Illuminate\Support\Facades\Schedule;
Schedule::command('session:garbage-collect')->hourly();
بحسب المصدر نفسه:
- يعتمد الأمر على قيمة
session.lifetimeلتحديد الجلسات المنتهية. - لمنع الطلبات من تشغيل التنظيف بنفسها، اضبط الـ lottery على
[0, 100].
متى يفيدك؟
الدرايفرات التي تنتهي صلاحية بياناتها تلقائيًا، مثل Redis وMemcached، لا تحتاج هذا التنظيف أصلًا (توثيق Laravel لواجهة gc في الدرايفرات المخصصة يقول إنها قد تُترك فارغة في الأنظمة ذاتية الانتهاء). الفائدة الحقيقية للدرايفرات التي تخزن الجلسات في جدول أو ملفات، وتذكر وثائق الجلسات أن درايفر database هو الافتراضي في Laravel 13، فقد تستفيد من الأمر مباشرة إن لم تغيّر الإعداد.
موديلات بلا مفتاح أساسي
جدول مثل سجل الأنشطة لا يحتاج عمود id غالبًا. قبل هذا الإصدار كان تمرير null للمفتاح يعود إلى الخاصية $primaryKey الافتراضية id. الآن يمكنك تعطيل المفتاح من خلال الـ attribute (PR رقم 62113 من Caleb White):
#[Table(name: 'activity_log', key: false)]
class Activity extends Model {}
Activity::create(['event' => 'login']);
وأضاف PR لاحق (رقم 62205) إصلاحًا يجعل هذه الموديلات غير تزايدية (non-incrementing)، فلا يرمي create() خطأً بعد الإدراج. تذكّر أن أي ميزة تعتمد على المفتاح، مثل find() أو بعض العلاقات، لن تكون منطقية لهذا الموديل.
كتابة ملفات الكاش بشكل ذري
هذا التغيير أهم مما يبدو. الأوامر config:cache وroute:cache وevent:cache أصبحت تكتب إلى ملف مؤقت ثم تعيد تسميته (PR رقم 62117). السبب الذي ذكره المصدر: طلب يبدأ أثناء تنفيذ الأمر قد يقرأ ملفًا فارغًا، ومع ضبط opcache.validate_timestamps=0 يحتفظ OPcache بالملف الفارغ فتستمر الأخطاء حتى إعادة تشغيل PHP-FPM.
عمليًا: إن كنت تنفّذ هذه الأوامر أثناء النشر على خادم يستقبل حركة، فهذا الإصلاح يقلل نافذة خطر حقيقية دون أي تغيير في كودك.
مدة تنفيذ المهام الفاشلة في JobAttempted
كان الحدث JobProcessed وحده يحمل المدة، فلا توقيت للمهام الفاشلة أو المُعادة إلى الطابور. أضاف PR رقم 62118 من Jack Bayliss الخاصية duration (بالملي ثانية) إلى JobAttempted، وأضاف PR رقم 62155 الشيء نفسه للـ sync queue:
use Illuminate\Queue\Events\JobAttempted;
Event::listen(function (JobAttempted $event) {
if (! $event->successful()) {
Log::warning('Job failed', [
'job' => $event->job->resolveName(),
'duration_ms' => $event->duration,
]);
}
});
تفيدك هذه القيمة في رصد المهام التي تفشل بعد وقت طويل، فتفرّق بين فشل فوري وفشل مكلف يستهلك وقت الـ worker.
إصلاحات العلاقات في Eloquent
forceCreate()وforceCreateQuietly()متاحتان الآن علىBelongsToMany. قبل ذلك كانت الاستدعاءات تذهب إلى الـ query builder، فيُنشأ الموديل دون ربطه بالعلاقة.restoreOrCreate()وcreateOrRestore()تعملان عبر العلاقة، بما فيهاHasManyThroughوHasOneThrough. كانتا تتجاهلان العلاقة، فتُنشأ الموديلات في علاقات has-many بلا مفتاح أجنبي، وقد تُستعاد في belongs-to-many سجلات خاطئة.
$team->users()->forceCreate(['name' => 'Bob', 'is_admin' => true]);
لاحظ أن forceCreate() يتجاوز حماية الـ mass assignment، فلا تمرّر إليها مدخلات المستخدم مباشرة.
قواعد التحقق: دوال الحجم في Rule::array
بعد Rule Builders في 13.36، أضاف الإصدار الدوال min() وmax() وbetween() وexactly() إلى Rule::array() (PR رقم 62179):
'photos' => [Rule::required(), Rule::array()->min(1)->max(10)],
تحسينات الاختبار
- الـ attribute الجديد
#[WithoutMigrationEvents]يخفي استعلامات الـ migration عنDB::listen()وexpectsDatabaseQueryCount()عند استخدامLazilyRefreshDatabase. Mail::html()وMail::plain()تعمل الآن مع الـ fake، وكانتا ترسلان بريدًا حقيقيًا أثناء الاختبار.RefreshDatabaseيتراجع عن كل مستويات الـ transaction بعد كل اختبار، فلا يفسد اختبارٌ ترك transaction مفتوحة ما بعده.- إصلاح
assertSeeText('0')الذي كان ينجح دون فحص فعلي.
إصلاحات تستحق الانتباه
من بين 57 تغييرًا في ملاحظات الإصدار، أبرز ما يمس الإنتاج:
- حجب الـ stream wrapper من نوع
phar://فيFilesystem::exists()وisFile(). - إصلاح
Http::batch()الذي كان يعدّ فشلretry()وthrow()نجاحًا. - إصلاح قفل الكاش في قاعدة PostgreSQL الذي كان يجهض الـ transaction.
- إصلاح تسرب الـ route السابق إلى طلبات 404 تحت Octane.
- إصلاح استعلامات N+1 في تضمينات JSON:API resource collections.
withoutMiddleware()أصبح يستثني الـ middleware ذات الوسائط.
كيف تتبنّى الإصدار؟
- حدّث في بيئة الاختبار:
composer update laravel/framework، وشغّل مجموعة اختباراتك. - إن كان درايفر الجلسات
databaseأوfile، أضف الأمر إلى الجدولة، واضبط الـ lottery على[0, 100]إذا أردت إيقاف التنظيف من الطلبات. - راجع أي اختبارات تعتمد على سلوك قديم، مثل
assertSeeText('0')أوMail::html()، فقد تبدأ بالفشل لأنها صارت تفحص فعلًا. - تأكد أن الـ scheduler يعمل فعلًا على خادمك (
php artisan schedule:runكل دقيقة)، وإلا لن يُنفَّذ الأمر المجدول.
ملاحظة: الأمثلة أعلاه مأخوذة من المصادر المذكورة، ولم أجرّبها على مشروع حي. اختبرها في بيئتك قبل الإنتاج.
الخلاصة
13.39 ليس إصدارًا استعراضيًا، لكنه من النوع الذي يريح الإنتاج: تنظيف الجلسات بالجدولة، وملفات كاش لا تُقرأ فارغة، وبيانات أوضح عن المهام الفاشلة. ابدأ بخطوة واحدة اليوم: جدول session:garbage-collect إن كنت تخزن الجلسات في قاعدة البيانات.