ترحيل Symfony دون انقطاع في الخدمة
يُعدّ ترحيل تطبيق Symfony من الإصدار 2.8 إلى 6.4 تحدياً كبيراً. في Keytchens، أنجزنا هذا الترحيل على منصة قيد الإنتاج تدير الطلبات في الزمن الحقيقي، دون أي انقطاع في الخدمة.
تفصل أربعة إصدارات رئيسية بين Symfony 2.8 وSymfony 6.4. وخلال هذه المسافة، تغيّرت بنية المجلدات، وأصبح حقن الاعتماديات تلقائياً، وأُعيدت كتابة نظام الأمان، وانتقل PHP من الإصدار 5 إلى الإصدار 8. يعرض هذا المقال طريقة لتجاوز هذه المراحل واحدة تلو الأخرى، مع إبقاء التطبيق في الإنتاج في كل لحظة.
المبدأ: اتّباع نموذج إصدارات Symfony
يُصدر Symfony نسخة فرعية (minor) كل ستة أشهر، وآخر نسخة فرعية من كل إصدار رئيسي (x.4) هي نسخة LTS. القاعدة الأساسية هي التالية: لا تُحذف أي ميزة في إصدار رئيسي دون أن تكون قد وُسمت بأنها مُهمَلة (deprecated) في الإصدار السابق. لذا فإن التطبيق الذي يعمل على آخر نسخة فرعية من إصدار رئيسي دون أي عنصر مُهمَل يمكنه الانتقال إلى الإصدار الرئيسي التالي دون أعطال.
وبذلك ينقسم الترحيل إلى مراحل، تُعتمد كل منها وتُنشر في الإنتاج قبل الانتقال إلى التي تليها.
استراتيجية ترحيل تدريجية
بدلاً من إعادة الكتابة الكاملة، اخترنا الترحيل التدريجي:
- المرحلة 1: Symfony 2.8 → 3.4 (التوافق التصاعدي)
- المرحلة 2: Symfony 3.4 → 4.4 (الانتقال إلى بنية Flex)
- المرحلة 3: Symfony 4.4 → 5.4 (إزالة العناصر المُهمَلة (deprecations))
- المرحلة 4: Symfony 5.4 → 6.4 (اعتماد PHP 8.1+)
تفرض كل مرحلة أيضاً حداً أدنى لإصدار PHP: PHP 7.1.3 لـ Symfony 4.4، وPHP 7.2.5 لـ Symfony 5.4، وPHP 8.1 لـ Symfony 6.4. غالباً ما يكون الأبسط ترقية PHP أولاً على إصدار Symfony الحالي، ثم ترحيل Symfony: فلا نغيّر إلا متغيّراً واحداً في كل مرة.
قبل البدء
- الاختبارات: من دون اختبارات وظيفية تغطي المسارات الحرجة، تصبح كل مرحلة مجازفة. ابدأ من هنا.
- جرد الاعتماديات: الأمر
composer outdatedوقائمة الحزم (bundles) الخارجية. أي حزمة مهجورة وغير متوافقة مع الإصدار المستهدف يجب استبدالها قبل الترحيل، لا أثناءه. - بيئة ما قبل الإنتاج (staging) مغذّاة بنسخة مجهّلة الهوية من بيانات الإنتاج.
- تتبّع الأخطاء (سجلات مركزية، أداة من نوع Sentry) لرصد أي تراجع فور النشر.
أدوات لا غنى عنها
يسرد PHPUnit Bridge الخاص بـ Symfony كل العناصر المُهمَلة التي تُستدعى أثناء الاختبارات. ويطبّق Rector جزءاً كبيراً من التصحيحات تلقائياً:
# Detect deprecations
composer require --dev symfony/phpunit-bridge
SYMFONY_DEPRECATIONS_HELPER='max[total]=0' php bin/phpunit
# Fix automatically
composer require --dev rector/rector
vendor/bin/rector process --dry-run
vendor/bin/rector process
مع max[total]=0، يكفي عنصر مُهمَل واحد لإفشال مجموعة الاختبارات: وهذا هو الإعداد الذي ينبغي بلوغه في التكامل المستمر (CI) قبل تغيير الإصدار الرئيسي. يُضبط Rector في ملف rector.php في جذر المشروع، باختيار مجموعة القواعد المطابقة للإصدار المستهدف:
<?php
use Rector\Config\RectorConfig;
use Rector\Symfony\Set\SymfonySetList;
return RectorConfig::configure()
->withPaths([__DIR__ . '/src', __DIR__ . '/tests'])
->withSets([SymfonySetList::SYMFONY_54]);
شغّل Rector دائماً مع --dry-run أولاً وراجع الفروقات (diff): إنه أداة قوية، لكنها ليست معصومة. ومن جهة حاوية الخدمات، يسرد الأمر php bin/console debug:container --deprecations (المتاح منذ Symfony 5.1) العناصر المُهمَلة التي تظهر أثناء الترجمة (compilation).
بدءاً من بنية Flex، يُتحكَّم في إصدار Symfony من خلال composer.json، ثم يُحدَّث بأمر واحد:
{
"extra": {
"symfony": {
"require": "6.4.*"
}
}
}
composer update "symfony/*" --with-all-dependencies
نقاط حرجة
التغيير الأبرز هو إعداد الخدمات. كان Symfony 2.8 يفرض التصريح بكل خدمة يدوياً؛ أما منذ Symfony 3.3، فإن الربط التلقائي (autowiring) والإعداد التلقائي (autoconfiguration) يسجّلان أصناف src/ تلقائياً:
# Service migration (before - services.yml)
services:
app.manager.order:
class: App\Manager\OrderManager
arguments: ['@doctrine.orm.entity_manager']
# After - services.yaml with autowiring
services:
_defaults:
autowire: true
autoconfigure: true
App\:
resource: '../src/'
إذا كانت بعض الشيفرة لا تزال تجلب الخدمة بمعرّفها القديم، مثل $container->get('app.manager.order')، فصرّح بـ alias مؤقت نحو الصنف، ثم استبدل هذه الاستدعاءات تدريجياً بالحقن عبر الباني (constructor). الخدمات خاصة (private) افتراضياً منذ Symfony 4.0: يجب أن يختفي الوصول المباشر إلى الحاوية.
نقاط أخرى تستحق الانتباه:
- بنية المجلدات (الانتقال إلى 4.x): يصبح
app/configهوconfig/، ويصبحweb/هوpublic/، وتنتقل القوالب إلىtemplates/، وتُسجَّل الحزم فيconfig/bundles.php. - الأمان: حلّ نظام authenticators الجديد محلّ نظام المصادقة Guard، وقد قُدّم في 5.1 وأصبح الوحيد المتاح في 6.0. يجب إعادة كتابة أدوات المصادقة المخصّصة.
- الأوامر والمتحكّمات: حُذف
ContainerAwareCommandفي 5.0، ويجب أن تتلقى المتحكّمات اعتمادياتها عبر الحقن بدلاً من$this->get(). - أنواع الإرجاع: يضيف Symfony 6 أنواع إرجاع أصلية (native return types) إلى واجهاته. ويجب على الأصناف التي تطبّقها أو ترثها (voters، normalizers، الأوامر) التصريح بالأنواع نفسها.
إدارة النشر دون توقف (zero-downtime)
للحفاظ على الخدمة أثناء الترحيل:
- نشر blue-green باستخدام Docker
- مفاتيح الميزات (feature flags) لتفعيل الميزات الجديدة تدريجياً
- اختبارات انحدار (regression) مؤتمتة تغطي 85% من الشيفرة
- تراجع تلقائي (rollback) عند اكتشاف أخطاء
يقوم نشر blue-green على تشغيل بيئتين جنباً إلى جنب: تستقبل النسخة الحالية الحركة بينما تبدأ النسخة الجديدة وتجتاز فحوص السلامة. ثم يحوّل الوكيل العكسي (reverse proxy) الحركة إليها، وتبقى النسخة القديمة متاحة لتراجع فوري. شرط لا غنى عنه: يجب أن تعمل النسختان مع مخطط قاعدة البيانات نفسه. لذا يجب أن تبقى ترحيلات Doctrine متوافقة مع الإصدارات السابقة، وفق المبدأ المفصّل في مقال ترحيلات قاعدة البيانات دون انقطاع.
تُكمل مفاتيح الميزات هذه المنظومة: تُنشر الشيفرة الجديدة وهي معطّلة، ثم تُفعَّل لجزء من المستخدمين، وتُوقَف في لحظة إذا ظهرت مشكلة.
أخطاء يجب تجنّبها
- تخطّي المراحل: القفز مباشرة من 2.8 إلى 4.4 يراكم مئات التغييرات ويجعل تحديد مصدر كل خطأ صعباً.
- خلط الترحيل بالميزات الجديدة: فرع ترحيل يعيش لأشهر يصبح دمجه مستحيلاً. سلّم كل مرحلة بسرعة، عبر pull requests صغيرة.
- تجاهل العناصر المُهمَلة في الحزم الخارجية: ستعطّل الإصدار الرئيسي التالي تماماً كما تفعل عناصرك أنت.
- نسيان الذاكرة المؤقتة (cache) وOPcache عند النشر: لا غنى عن
cache:clearوإعادة تحميل PHP-FPM بعد كل مرحلة.
قائمة التحقق لكل مرحلة
- ترقية PHP إلى الإصدار المطلوب والتحقق من الاختبارات.
- بلوغ صفر عناصر مُهمَلة على الإصدار الحالي.
- تحديث Symfony والحزم إلى الإصدار الرئيسي التالي.
- تشغيل Rector، ومراجعة الفروقات، وتصحيح الحالات المتبقية.
- الاعتماد في بيئة ما قبل الإنتاج، ثم النشر بأسلوب blue-green.
- مراقبة الأخطاء والأداء قبل الانتقال إلى المرحلة التالية.
أتاح هذا الترحيل تحسين الأداء بنسبة 40% وتقليص الدين التقني بشكل ملحوظ.