الترقية إلى PHP 8.4 على الإنتاج: خطة بلا مفاجآت

الترقية الآمنة إلى PHP 8.4 تبدأ بفحص الكود بـPHPStan وRector قبل لمس السيرفر، ثم تشغيل PHP-FPM 8.4 بجانب النسخة الحالية وتحويل موقع واحد في كل مرة، مع خطة رجوع لا تتجاوز دقيقة واحدة بتعديل سطر في Nginx.
الترقية إلى PHP 8.4 على سيرفر إنتاج لا تحتاج نافذة توقف طويلة ولا مغامرة. الخطة التي نوصي بها من أربع مراحل: أولاً تفحص الكود آلياً بـPHPStan وRector وPHPCompatibility لتعرف ما سينكسر قبل أن ينكسر، ثانياً تثبّت PHP-FPM 8.4 بجانب النسخة الحالية على نفس السيرفر، فيصير عندك مقبسان (socket) منفصلان مثل php8.3-fpm.sock وphp8.4-fpm.sock، ثالثاً تختبر على بيئة staging بنسخة من قاعدة البيانات، رابعاً تحوّل كل موقع على حدة بتعديل سطر fastcgi_pass في Nginx ثم nginx -t وsystemctl reload nginx. الرجوع عند أي مشكلة هو نفس السطر بالعكس، ويستغرق أقل من دقيقة بلا إعادة تشغيل ولا انقطاع اتصالات قائمة. PHP 8.4 صدرت في نوفمبر 2024، والنسخة 8.1 خرجت من الدعم الأمني نهاية 2025، لذلك صارت الترقية مسألة أمان لا رفاهية. أغلب تطبيقات Laravel 11 و12 تعمل على 8.4 بلا تعديل، والمشاكل الحقيقية تأتي عادة من حزم قديمة في composer.lock أو امتدادات أُزيلت من النواة.
ما الذي يتغير فعلاً في PHP 8.4
الإضافات الجديدة لن تكسر كودك لأنها اختيارية، لكن من المفيد أن تعرفها لأنك ستستخدمها بعد الترقية:
- Property hooks: تكتب منطق
getوsetداخل تعريف الخاصية نفسها بدل دوال getter وsetter منفصلة. - Asymmetric visibility: مثل
public private(set) string $status، أي القراءة عامة والكتابة من داخل الكلاس فقط. - دوال مصفوفات جديدة:
array_findوarray_find_keyوarray_anyوarray_all. - استدعاء دالة على كائن جديد بلا أقواس إضافية:
new Invoice()->total(). - محلل HTML5 جديد عبر
Dom\HTMLDocument، ودالةmb_trimللنصوص متعددة البايت مثل العربية.
أما ما قد يكسر شيئاً أو يملأ السجلات بتحذيرات فهذه القائمة:
| التغيير | الأثر | الإصلاح |
|---|---|---|
إهمال النوع nullable الضمني function f(User $u = null) | تحذير Deprecated في كل استدعاء | ?User $u = null، ويصلحه Rector آلياً |
| نقل IMAP وOCI8 وPDO_OCI وpspell إلى PECL | خطأ fatal إن كان كودك يعتمد عليها | تثبيتها من PECL أو استبدالها |
إهمال E_STRICT وtrigger_error مع E_USER_ERROR | تحذيرات | استبدالها باستثناءات |
| رفع كلفة bcrypt الافتراضية من 10 إلى 12 | تسجيل الدخول أبطأ بنحو 4 أضعاف لكل عملية تجزئة | مقبول أمنياً، راقب المعالج في صفحات الدخول |
النقطة الأخيرة تستحق انتباهاً: عملية bcrypt بكلفة 12 تأخذ نحو 200 إلى 250 مللي ثانية على نواة حديثة، فإذا كانت عندك واجهة API تتحقق من كلمات المرور مع كل طلب فستلاحظ ارتفاع المعالج فوراً.
فحص التوافق قبل أن تلمس السيرفر
هذه المرحلة تتم على جهاز المطور أو في CI، ولا علاقة لها بالسيرفر. ابدأ بإعلام Composer بالنسخة الهدف حتى يرفض أي حزمة لا تدعمها:
composer config platform.php 8.4.0
composer update --dry-run
composer why-not php 8.4
أمر why-not يسرد لك كل حزمة تمنع الترقية، وفي أغلب المشاريع التي فحصناها تكون حزمتين أو ثلاثاً قديمة تحتاج ترقية رقم رئيسي. بعدها شغّل Rector بوضع التجربة ليريك ما سيعدّله دون أن يلمس شيئاً:
// rector.php
return RectorConfig::configure()
->withPaths([__DIR__ . '/app', __DIR__ . '/tests'])
->withPhpSets(php84: true);
vendor/bin/rector process --dry-run
ثم PHPStan مع تحديد النسخة، لأنه يلتقط استدعاءات لدوال أُزيلت أو تغيّر توقيعها:
# phpstan.neon
parameters:
level: 5
phpVersion: 80400
vendor/bin/phpstan analyse
أخيراً شغّل مجموعة الاختبارات كاملة على PHP 8.4 محلياً مع error_reporting=E_ALL، فكل تحذير Deprecated يظهر الآن هو خطأ fatal محتمل في PHP 9. مشروع Laravel متوسط بـ300 اختبار ينهي هذه المرحلة كلها في أقل من ساعة.
تشغيل نسختين PHP-FPM جنباً إلى جنب
هنا جوهر الخطة: لا تستبدل النسخة القديمة، بل أضف الجديدة بجانبها. على Ubuntu وDebian يوفر مستودع ondrej/php كل النسخ بالتوازي:
add-apt-repository ppa:ondrej/php
apt update
apt install php8.4-fpm php8.4-cli php8.4-mysql php8.4-redis \
php8.4-mbstring php8.4-xml php8.4-curl php8.4-zip php8.4-intl php8.4-bcmath
systemctl enable --now php8.4-fpm
الآن عندك عمليتا FPM تعملان معاً، لكل منهما ملف php.ini مستقل وذاكرة OPcache مستقلة ومقبس مستقل. انسخ إعدادات الحوض (pool) من /etc/php/8.3/fpm/pool.d/www.conf إلى المسار المقابل في 8.4، وخاصة pm.max_children وmemory_limit وupload_max_filesize، لأن النسخة الجديدة تأتي بالقيم الافتراضية. انتبه للذاكرة: عمليتا FPM بـ20 عاملاً لكل منهما على سيرفر بـ4GB قد تستهلكان أكثر من 2GB، لذلك خفّض pm.max_children في النسخة غير المستخدمة مؤقتاً، أو اجعلها pm = ondemand حتى لا تحجز ذاكرة بلا داع.
إن كان موقعك على استضافة cPanel فالمبدأ نفسه موجود في MultiPHP Manager، حيث تختار نسخة PHP لكل نطاق على حدة، وهذا متاح في الاستضافة السحابية المُدارة دون أي أوامر.
الاختبار على staging
أنشئ نطاقاً فرعياً مثل staging.example.sa يشير لنسخة من الكود ونسخة حديثة من قاعدة البيانات، واجعل كتلة Nginx الخاصة به تستخدم مقبس 8.4. مرّ على المسارات الحرجة يدوياً: تسجيل الدخول، والدفع، وتوليد ملفات PDF، ورفع الملفات، والتقارير الثقيلة. راقب /var/log/php8.4-fpm.log وسجل Laravel لأي Deprecated أو Warning جديد. لا تنس عمّال الطوابير ومهام cron، فهي تعمل من سطر الأوامر وليس من FPM، وكثير من الترقيات تنجح في الواجهة ثم تفشل في مهمة مجدولة تعمل الساعة 3 فجراً.
نافذة التبديل وخطة الرجوع بدقيقة واحدة
اختر وقتاً منخفض الحركة، ثم عدّل كتلة الموقع في Nginx:
# قبل
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
# بعد
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
nginx -t && systemctl reload nginx
الأمر reload لا يقطع أي طلب قائم، فالعمّال القدامى يكملون طلباتهم والجدد يستخدمون المقبس الجديد. بعدها حوّل سطر الأوامر وأعد تشغيل العمّال:
update-alternatives --set php /usr/bin/php8.4
php artisan queue:restart
php artisan optimize
راقب أول 15 دقيقة ثلاثة أرقام: نسبة أخطاء 5xx في سجل Nginx، وزمن الاستجابة، وحجم سجل الأخطاء. إذا ظهرت مشكلة فالرجوع هو نفس السطرين بالعكس مع reload، والنسخة القديمة ما زالت تعمل ودافئة بذاكرة OPcache جاهزة، لذلك يتم الرجوع في أقل من 60 ثانية. أبقِ النسخة القديمة مثبتة أسبوعين على الأقل قبل حذفها، فبعض المشاكل لا تظهر إلا مع تقرير يُشغّل مرة في الأسبوع.
مثال عملي بعد الترقية: property hooks بدل getters
بعد أن تستقر على 8.4 تستطيع تبسيط كود كثير. خذ مثلاً كلاس فاتورة يحسب الضريبة 15% ويمنع تعديل الحالة من الخارج، كان يحتاج سابقاً خاصية خاصة ودالتين لكل قيمة، وصار الآن بهذا الشكل:
final class Invoice
{
public private(set) string $status = 'draft';
public function __construct(public float $subtotal) {}
public float $total {
get => round($this->subtotal * 1.15, 2);
}
public function markPaid(): void
{
$this->status = 'paid';
}
}
أي محاولة لكتابة $invoice->status = 'paid' من خارج الكلاس ترمي خطأ مباشرة، والخاصية total تُحسب عند القراءة فلا تخزن قيمة قديمة. لا تستعجل في إعادة كتابة كل الكود بهذا الأسلوب، فالترقية والتحسين مرحلتان منفصلتان، ودمجهما في نشر واحد يجعل تتبع أي خطأ أصعب بكثير. ابدأ بالترقية وحدها، ثم استخدم الميزات الجديدة في الكود الجديد فقط خلال الأسابيع التالية.
OPcache وJIT على النسخة الجديدة
كل نسخة FPM تحمل ذاكرة OPcache خاصة بها، لذلك أول طلبات بعد التبديل تكون أبطأ قليلاً حتى تُترجم الملفات وتُخزن. في تطبيق Laravel متوسط بنحو 4,000 ملف PHP يأخذ هذا من 2 إلى 5 ثوانٍ من الحركة العادية. تأكد أن opcache.memory_consumption لا يقل عن 256 وأن opcache.max_accelerated_files لا يقل عن 20,000 في ملف 8.4، لأن القيم الافتراضية أصغر من حاجة أغلب تطبيقات الإنتاج. أما JIT فاتركه معطلاً لتطبيقات الويب العادية، فمكسبه يظهر في الحسابات الثقيلة لا في طلبات قاعدة البيانات.
كم يكلف هذا من موارد
تشغيل نسختين معاً لفترة انتقالية يحتاج ذاكرة إضافية تتراوح بين 300MB و1GB حسب عدد العمّال. على خادم سحابي بباقة أعمال (4 vCPU و8GB) بـ11,500 ريال سنوياً هذا هامش مريح، أما على باقة أساس بـ2GB فالأفضل أن تحوّل المواقع بسرعة أو تضيف 2GB RAM بـ600 ريال سنوياً خلال فترة الانتقال. تفاصيل الباقات والإضافات في صفحة الأسعار.
قائمة التنفيذ المختصرة
- اضبط
platform.phpعلى 8.4.0 وشغّلcomposer why-not php 8.4ثم رقّ الحزم المعترضة. - شغّل Rector بـ
--dry-runوPHPStan بـphpVersion: 80400وأصلح النتائج. - شغّل الاختبارات كاملة على 8.4 مع إظهار كل الإهمالات.
- ثبّت
php8.4-fpmبجانب الحالية وانسخ إعدادات الحوض. - اختبر على staging بما فيه الطوابير وcron.
- بدّل موقعاً واحداً، راقب 15 دقيقة، ثم انتقل للموقع التالي.
- احذف النسخة القديمة بعد أسبوعين بلا مشاكل.


