Laravel 13.35: جدولة على سيرفر واحد وQUERY route وذاكرة الـ Worker
Laravel 13.35 يضيف Schedule::alwaysOnOneServer() ودعم Route::query() ونسبة مئوية لـ --memory. إليك ما يهمّك قبل الترقية وكيف تستفيد منه.
صدر Laravel Framework v13.35.0 في 6 أكتوبر بحسب صفحة الإصدارات على GitHub، ويضم عشرات الـ pull requests من أكثر من 30 مساهمًا. معظمها إصلاحات في Eloquent والـ collections، لكن هناك ثلاث إضافات عملية تمسّ الإنتاج مباشرة: الـ scheduler، والـ queue worker، والـ router. في هذا المقال نشرح كل واحدة، مع مثال، وما الذي يجب الانتباه إليه. (الإصدار السابق 13.34 غطّيناه في مقال مستقل.)
Schedule::alwaysOnOneServer(): جدولة بلا تكرار
إذا شغّلت التطبيق على أكثر من نسخة (replicas)، فكل نسخة تشغّل schedule:run، فتتكرر المهام المجدولة. الحل المعروف هو onOneServer()، لكن الـ PR رقم #61789 يشير إلى مشكلته: يجب أن يتذكر كل مطوّر استخدامه، وإلا قد ينسى عضو جديد في الفريق إضافته، أو تضطر لتغليف الجدول كله بـ Schedule::onOneServer()->group(...).
الجديد أنك تستدعي دالة واحدة، مرة واحدة، في service provider:
// app/Providers/AppServiceProvider.php
use Illuminate\Support\Facades\Schedule;
public function boot(): void
{
Schedule::alwaysOnOneServer();
}
بحسب وصف الـ PR:
- السلوك معطّل افتراضيًا، فلا يتأثر أي تطبيق قائم.
- تُتجاهل الـ closures المجدولة التي لا اسم لها بدل أن تسبب خطأ. فإن كانت عندك مهام closure، فاستخدم
->name('...')لتتأكد أنها ضمن الحماية.
ملاحظة: صفحة الـ PR لا تذكر متطلبات الـ cache. بما أن السيرفرات يجب أن تتفق على من ينفّذ المهمة، فراجع وثائق Laravel الرسمية عن onOneServer() للتأكد من درايفر الـ cache المناسب لإعدادك قبل الاعتماد عليها في الإنتاج.
schedule:interrupt للأوامر الطويلة
الـ PR رقم #61764 يعالج مشكلة في النشر (deployment). الأمر المجدول الذي يعمل بـ runInBackground() لم يكن يعرف أن schedule:interrupt نُفّذ، لأن علامة الإيقاف كانت قيمة boolean تنتهي مع نهاية الدقيقة. النتيجة: إما أن ينتظر النشر انتهاء الأمر الطويل أو يقتله.
الآن تُخزَّن الـ interrupt كطابع زمني، على طريقة queue:restart، وتوجد دالة Schedule::interruptedSince($startedAt) تفحصها في نقاط آمنة داخل الأمر:
public function handle(Schedule $schedule)
{
$startedAt = now();
foreach ($this->batches() as $batch) {
if ($schedule->interruptedSince($startedAt)) {
return; // التشغيل المجدول التالي يكمل من هنا
}
$this->process($batch);
}
}
كذلك لم تعد الـ interrupt تنتقل إلى تشغيل يبدأ لاحقًا في الدقيقة نفسها. الفكرة العملية: صمّم أوامرك الطويلة بحيث تعالج دفعات صغيرة وتكون قابلة للاستئناف، ثم افحص الـ interrupt بين الدفعات.
queue:work --memory بالنسبة المئوية
الـ worker يعيد تشغيل نفسه عندما يتجاوز حدًّا للذاكرة. حتى الآن كان الخيار --memory يقبل قيمة مطلقة بالميغابايت فقط. بعد الـ PR رقم #61753 يمكنك كتابة نسبة من memory_limit في PHP:
php artisan queue:work --memory=60%
الفائدة أنك لا تحتاج إلى إعادة حساب القيمة كلما تغيّر حد الذاكرة في الحاوية (container) أو على جهاز مطوّر آخر. بحسب كاتب الـ PR:
- التغيير ليس breaking.
- لا يؤثر على الـ queues المُدارة في Laravel Cloud لأنها لا تقبل خيارات worker مخصصة.
- لا تضع القيمة عند الحد الأقصى؛ اترك هامشًا ليتمكن الـ worker من إنهاء الـ job الحالية وإعادة التشغيل بسلام.
- أُغلق طلب منفصل (#61836) كان سيضيف الدعم نفسه إلى
queue:listen، فالنسبة متاحة لـqueue:workفقط.
Route::query(): دعم HTTP QUERY
أضاف الـ PR رقم #61797 (backport من #60655) الدالة Route::query() لتسجيل routes تستجيب لـ HTTP method اسمها QUERY، وهي طريقة تحمل body مثل POST لكنها «آمنة» بحسب مواصفاتها، وتناسب استعلامات البحث المعقدة التي لا تتسع في الـ query string:
Route::query('/products/search', SearchProductsController::class);
وكانت مساعدات الاختبار query() وqueryJson() متاحة أصلًا في 13.x بـ PR آخر (#60662). انتبه إلى هذه النقاط:
- حماية CSRF ما زالت مفعّلة لطلبات QUERY، بخلاف الـ PR الأصلي الذي عاملها كطريقة للقراءة فقط. لاستثناء route معيّن استخدم
preventRequestForgery(except: [...]). - المتصفحات تُجري preflight للطلبات cross-origin من نوع QUERY، لكن هذا لا يحميك إلا إذا كانت إعدادات CORS صارمة.
- بما أن QUERY أُضيفت إلى
Router::$verbs، فإنRoute::any()وRoute::redirect()تطابقان الآن طلبات QUERY أيضًا، وroute:listيتأثر عند ظهورANY. راجع routes من هذين النوعين. - لم يتغير عقد
Registrar، فإذا حقنت الـ contract بدل الصنف الفعلي ستفقد جزءًا من الإكمال التلقائي في الـ IDE.
تغييرات صغيرة تستحق نظرة
Response::markdown()في response factory (#61800).- خيار
lazy_root_creationلأقراصlocal(#61799). - قبول الـ enums في
FilesystemManager::set()وforgetDisk()وpurge()(#61868). - إصلاحات Eloquent كثيرة، منها
chunkById()وlazyById()اللتان كانتا تدوران بلا نهاية مع primary key له cast (#61816)، وforceCreate()الذي كان يتجاهلwithAttributes()(#61823). - إضافة
onOneServerإلى مخرجاتschedule:list --json(#61798).
كيف تتبنّى Laravel 13.35 في مشروعك؟
- حدّث بـ
composer update laravel/frameworkوشغّل الاختبارات. صفحة الإصدار لا تصنّف أي تغيير على أنه breaking. - إن كنت تشغّل أكثر من نسخة، فجرّب
Schedule::alwaysOnOneServer()على بيئة staging أولًا، وسمِّ الـ closures المجدولة. - استبدل قيمة
--memoryالثابتة في ملفات supervisor أو الحاويات بنسبة مثل60%، وراقب إعادة تشغيل الـ workers. - ابحث في مشروعك عن
Route::any()وRoute::redirect()وتأكد أن قبولهما لـ QUERY لا يسبب مفاجأة.
الخلاصة
الإصدار 13.35 ليس ثوريًا، لكنه يزيل ثلاث نقاط احتكاك يومية: نسيان onOneServer()، وقيم الذاكرة الثابتة للـ worker، والأوامر المجدولة الطويلة أثناء النشر. أما Route::query() فهي إضافة جديدة تحتاج إلى حذر في CSRF وCORS قبل استخدامها في API عام.