الدليل الشامل لتحويل كود PHP
نشرت: 2021-09-22في الظروف المثالية ، يجب أن نستخدم PHP 8.0 (أحدث إصدار حتى كتابة هذا المقال) لجميع مواقعنا وتحديثه بمجرد إصدار إصدار جديد. ومع ذلك ، سيحتاج المطورون غالبًا إلى العمل مع إصدارات PHP السابقة ، على سبيل المثال عند إنشاء مكون إضافي عام لبرنامج WordPress أو العمل باستخدام رمز قديم يعيق ترقية بيئة خادم الويب.
في هذه المواقف ، قد نفقد الأمل في استخدام أحدث كود PHP. ولكن هناك بديل أفضل: لا يزال بإمكاننا كتابة الكود المصدري الخاص بنا باستخدام PHP 8.0 وتحويله إلى إصدار PHP سابق - حتى إلى PHP 7.1.
في هذا الدليل ، سنعلمك كل ما تحتاج لمعرفته حول تحويل كود PHP.
ما هو النقل؟
Transpiling يحول الكود المصدري من لغة البرمجة إلى كود مصدر مكافئ له نفس لغة البرمجة أو بلغة مختلفة.
Transpiling ليس مفهومًا جديدًا في تطوير الويب: من المحتمل جدًا أن يكون مطورو جانب العميل على دراية بـ Babel ، وهو مترجم لرمز JavaScript.
يقوم Babel بتحويل كود JavaScript من إصدار ECMAScript 2015+ الحديث إلى إصدار قديم متوافق مع المتصفحات القديمة. على سبيل المثال ، بالنظر إلى وظيفة السهم ES2015:
[2, 4, 6].map((n) => n * 2);
… ستحوله شركة Babel إلى إصدار ES5 الخاص بها:
[2, 4, 6].map(function(n) { return n * 2; });
ما هو Transpiling PHP؟
ما هو جديد محتمل في تطوير الويب هو إمكانية تحويل التعليمات البرمجية من جانب الخادم ، ولا سيما PHP.
يعمل Transpiling PHP بنفس طريقة تحويل JavaScript: يتم تحويل الكود المصدري من إصدار PHP الحديث إلى كود مكافئ لإصدار PHP أقدم.
باتباع نفس المثال السابق ، وظيفة السهم من PHP 7.4:
$nums = array_map(fn($n) => $n * 2, [2, 4, 6]);
... يمكن تحويلها إلى إصدار PHP 7.3 المكافئ لها:
$nums = array_map( function ($n) { return $n * 2; }, [2, 4, 6] );
يمكن نقل وظائف السهم لأنها عبارة عن سكر نحوي ، أي بناء جملة جديد لإنتاج سلوك موجود. هذه هي الفاكهة المتدلية.
ومع ذلك ، هناك أيضًا ميزات جديدة تنشئ سلوكًا جديدًا ، وعلى هذا النحو ، لن يكون هناك رمز مكافئ للإصدارات السابقة من PHP. هذا هو الحال مع أنواع الاتحاد المقدمة في PHP 8.0:
function someFunction(float|int $param): string|float|int|null { // ... }
في هذه الحالات ، لا يزال من الممكن إجراء النقل طالما أن الميزة الجديدة مطلوبة للتطوير وليس للإنتاج. بعد ذلك ، يمكننا ببساطة إزالة الميزة تمامًا من الشفرة المنقولة دون عواقب وخيمة.
أحد الأمثلة على ذلك هو أنواع الاتحاد. تُستخدم هذه الميزة للتحقق من عدم وجود عدم تطابق بين نوع الإدخال والقيمة المقدمة ، مما يساعد على منع الأخطاء. إذا كان هناك تعارض مع الأنواع ، فسيكون هناك خطأ قيد التطوير بالفعل ، ويجب علينا اكتشافه وإصلاحه قبل أن يصل الرمز إلى الإنتاج.
وبالتالي ، يمكننا إزالة الميزة من الكود للإنتاج:
function someFunction($param) { // ... }
إذا استمر حدوث الخطأ في الإنتاج ، فستكون رسالة الخطأ التي تم إلقاؤها أقل دقة مما لو كان لدينا أنواع نقابية. ومع ذلك ، فإن هذا العيب المحتمل يفوقه القدرة على استخدام أنواع النقابات في المقام الأول.
للتغريد
مزايا Transpiling PHP Code
يُمكّن Transpiling المرء من برمجة تطبيق باستخدام أحدث إصدار من PHP وإنتاج إصدار يعمل أيضًا في البيئات التي تعمل بإصدارات أقدم من PHP.
يمكن أن يكون هذا مفيدًا بشكل خاص للمطورين الذين ينشئون منتجات لأنظمة إدارة المحتوى القديمة (CMS). على سبيل المثال ، لا يزال WordPress يدعم PHP 5.6 رسميًا (على الرغم من أنه يوصى باستخدام PHP 7.4+). النسبة المئوية لمواقع WordPress التي تشغل إصدارات PHP 5.6 إلى 7.2 - والتي تعد جميعها End-of-Life (EOL) ، مما يعني أنها لم تعد تتلقى تحديثات أمنية بعد الآن - تبلغ 34.8٪ ، وتلك التي تعمل على أي إصدار PHP بخلاف 8.0 تقف عند نسبة هائلة تصل إلى 99.5٪:

وبالتالي ، من المحتمل جدًا أن يتم ترميز سمات WordPress والإضافات التي تستهدف جمهورًا عالميًا بإصدار قديم من PHP لزيادة وصولها المحتمل. بفضل التحويل ، يمكن ترميزها باستخدام PHP 8.0 ، ولا يزال يتم إصدارها لإصدار أقدم من PHP ، وبالتالي استهداف أكبر عدد ممكن من المستخدمين.
في الواقع ، يمكن أن يستفيد أي تطبيق يحتاج إلى دعم أي إصدار PHP بخلاف أحدث إصدار (حتى ضمن نطاق إصدارات PHP المدعومة حاليًا).
هذا هو الحال مع دروبال الذي يتطلب PHP 7.3. بفضل التحويل البرمجي ، يمكن للمطورين إنشاء وحدات دروبال متاحة للجمهور باستخدام PHP 8.0 ، وإصدارها باستخدام PHP 7.3.
مثال آخر هو عند إنشاء كود مخصص للعملاء الذين لا يستطيعون تشغيل PHP 8.0 في بيئاتهم لسبب أو لآخر. ومع ذلك ، بفضل التحويل البرمجي ، لا يزال بإمكان المطورين ترميز مخرجاتهم باستخدام PHP 8.0 وتشغيلها في تلك البيئات القديمة.
متى يتم ترجمة PHP
يمكن دائمًا تحويل كود PHP ما لم يكن يحتوي على بعض ميزات PHP التي لا مثيل لها في الإصدار السابق من PHP.
من المحتمل أن يكون هذا هو الحال مع السمات المقدمة في PHP 8.0:
#[SomeAttr] function someFunc() {} #[AnotherAttr] class SomeClass {}
في المثال السابق باستخدام وظائف السهم ، يمكن تحويل الشفرة لأن وظائف السهم عبارة عن سكر نحوي. السمات ، في المقابل ، تخلق سلوكًا جديدًا تمامًا. يمكن أيضًا إعادة إنتاج هذا السلوك باستخدام PHP 7.4 والإصدارات الأقل ، ولكن فقط عن طريق ترميزه يدويًا ، أي أنه لا يعتمد تلقائيًا على أداة أو عملية (يمكن أن يوفر الذكاء الاصطناعي حلاً ، لكننا لم نصل إليه بعد).
السمات المخصصة للاستخدام في التطوير ، مثل #[Deprecated]
، يمكن إزالتها بنفس طريقة إزالة أنواع الاتحاد. لكن السمات التي تعدل سلوك التطبيق في الإنتاج لا يمكن إزالتها ، ولا يمكن نقلها مباشرة أيضًا.
اعتبارًا من اليوم ، لا يمكن لأي مترجم أن يأخذ رمزًا بسمات PHP 8.0 وينتج تلقائيًا كود PHP 7.4 المكافئ. وبالتالي ، إذا احتاج كود PHP الخاص بك إلى استخدام سمات ، فسيكون تحويلها صعبًا أو غير مجدٍ.
ميزات PHP التي يمكن نقلها
هذه هي الميزات من PHP 7.1 وما فوق والتي يمكن تحويلها حاليًا. إذا كانت التعليمات البرمجية الخاصة بك تستخدم هذه الميزات فقط ، فيمكنك الاستمتاع بالتأكد من أن التطبيق المترجم سيعمل. خلاف ذلك ، سوف تحتاج إلى تقييم ما إذا كانت الشفرة المترجمة ستؤدي إلى فشل.
إصدار PHP | سمات |
---|---|
7.1 | كل شىء |
7.2 | - نوع object - اتساع نوع المعلمة - علم PREG_UNMATCHED_AS_NULL في preg_match |
7.3 | - التخصيصات المرجعية في list() / إتلاف المصفوفة ( ماعدا داخل foreach - # 4376)- تركيب Heredoc و Nowdoc المرن - زائدة الفواصل في وظائف المكالمات - set(raw)cookie يقبل وسيطة الخيار $ |
7.4 | - الخصائص المكتوبة - وظائف السهم - عامل تخصيص الاندماج الفارغ - تفريغ داخل المصفوفات - فاصل حرفي رقمي - strip_tags() مع مجموعة من أسماء العلامات- أنواع الإرجاع المتغايرة وأنواع المعلمات المتناقضة |
8.0 | - أنواع الإتحادات - النوع الزائف mixed - نوع إرجاع static - ::class السحر على الأشياء- match التعبيرات- catch الاستثناءات فقط حسب النوع- عامل آمن فارغ - ترويج ممتلكات منشئ الفئة - الفواصل اللاحقة في قوائم المعلمات وقوائم use الإغلاق |
PHP Transpilers
حاليًا ، هناك أداة واحدة لتحويل كود PHP: Rector.
Rector هي أداة إعادة بناء PHP ، والتي تقوم بتحويل كود PHP بناءً على قواعد قابلة للبرمجة. نقوم بإدخال الكود المصدري ومجموعة القواعد للتشغيل ، وسيقوم Rector بتحويل الكود.
يتم تشغيل Rector عبر سطر الأوامر المثبت في المشروع عبر Composer. عند التنفيذ ، سينتج Rector "فرق" (الإضافات باللون الأخضر ، والإزالة باللون الأحمر) للشفرة قبل التحويل وبعده:

أي إصدار من PHP يمكن التحويل إليه
لتحويل الشفرة عبر إصدارات PHP ، يجب إنشاء القواعد المقابلة.
تضم مكتبة Rector اليوم معظم القواعد الخاصة بترجمة الشفرة ضمن نطاق PHP 8.0 إلى 7.1. وبالتالي ، يمكننا ترجمة كود PHP الخاص بنا بشكل موثوق به حتى الإصدار 7.1.
هناك أيضًا قواعد للترجمة من PHP 7.1 إلى 7.0 ومن 7.0 إلى 5.6 ، لكنها ليست شاملة. يجري العمل على إكمالها ، لذلك قد نقوم في النهاية بتحويل كود PHP إلى الإصدار 5.6.
Transpiling مقابل Backporting
Backporting يشبه النقل ، ولكنه أبسط. لا تعتمد التعليمات البرمجية Backporting بالضرورة على ميزات جديدة من اللغة. بدلاً من ذلك ، يمكن توفير نفس الوظيفة لإصدار أقدم من اللغة ببساطة عن طريق نسخ / لصق / تكييف الكود المقابل من الإصدار الجديد للغة.
على سبيل المثال ، تم تقديم الوظيفة str_contains
في PHP 8.0. يمكن تنفيذ نفس الوظيفة لـ PHP 7.4 وما دون بسهولة كما يلي:
if (!defined('PHP_VERSION_ID') || (defined('PHP_VERSION_ID') && PHP_VERSION_ID < 80000)) { if (!function_exists('str_contains')) { /** * Checks if a string contains another * * @param string $haystack The string to search in * @param string $needle The string to search * @return boolean Returns TRUE if the needle was found in haystack, FALSE otherwise. */ function str_contains(string $haystack, string $needle): bool { return strpos($haystack, $needle) !== false; } } }
نظرًا لأن backporting أبسط من النقل ، يجب أن نختار هذا الحل كلما قام backporting بالمهمة.
فيما يتعلق بالنطاق بين PHP 8.0 إلى 7.1 ، يمكننا استخدام مكتبات polyfill الخاصة بـ Symfony:
- Polyfill PHP 7.1.1 تحديث
- Polyfill PHP 7.2.1 تحديث
- Polyfill PHP 7.3.2 تحديث
- Polyfill PHP 7.4.1 تحديث
- Polyfill PHP 8.0.0 تحديث
تدعم هذه المكتبات الوظائف والفئات والثوابت والواجهات التالية:
إصدار PHP | سمات |
---|---|
7.2 | المهام:
الثوابت:
|
7.3 | المهام:
استثناءات:
|
7.4 | المهام:
|
8.0 | واجهات:
الطبقات:
الثوابت:
المهام:
|
أمثلة من Transpiled PHP
دعنا نفحص بعض الأمثلة على كود PHP المنقول ، وبعض الحزم التي يتم نقلها بالكامل.
كود PHP
تم تقديم تعبير match
في PHP 8.0. كود المصدر هذا:
function getFieldValue(string $fieldName): ?string { return match($fieldName) { 'foo' => 'foofoo', 'bar' => 'barbar', 'baz' => 'bazbaz', default => null, }; }
… إلى إصدار PHP 7.4 المكافئ له ، باستخدام عامل تشغيل switch
:
function getFieldValue(string $fieldName): ?string { switch ($fieldName) { case 'foo': return 'foofoo'; case 'bar': return 'barbar'; case 'baz': return 'bazbaz'; default: return null; } }
تم تقديم العامل nullsafe أيضًا في PHP 8.0:
public function getValue(TypeResolverInterface $typeResolver): ?string { return $this->getResolver($typeResolver)?->getValue(); }
يحتاج الكود المنقول إلى تعيين قيمة العملية لمتغير جديد أولاً ، لتجنب تنفيذ العملية مرتين:
public function getValue(TypeResolverInterface $typeResolver): ?string { return ($val = $this->getResolver($typeResolver)) ? $val->getValue() : null; }
ميزة ترويج خاصية المُنشئ ، التي تم تقديمها أيضًا في PHP 8.0 ، تسمح للمطورين بكتابة كود أقل:
class QueryResolver { function __construct(protected QueryFormatter $queryFormatter) { } }
عند تحويلها إلى PHP 7.4 ، يتم إنتاج الجزء الكامل من الكود:
class QueryResolver { protected QueryFormatter $queryFormatter; function __construct(QueryFormatter $queryFormatter) { $this->queryFormatter = $queryFormatter; } }
تحتوي الشفرة المنقولة أعلاه على خصائص مكتوبة تم تقديمها في PHP 7.4. تحويل هذا الرمز إلى PHP 7.3 يستبدلهم بـ docblocks:
class QueryResolver { /** * @var QueryFormatter */ protected $queryFormatter; function __construct(QueryFormatter $queryFormatter) { $this->queryFormatter = $queryFormatter; } }
حزم PHP
يتم نقل المكتبات التالية للإنتاج:
المكتبة / الوصف | كود / ملاحظات |
---|---|
رئيس الجامعة أداة إعادة بناء PHP التي تجعل النقل ممكنًا | - مصدر الرمز - كود منقول - ملاحظات |
معايير تشفير سهلة أداة لجعل كود PHP يلتزم بمجموعة من القواعد | - مصدر الرمز - كود منقول - ملاحظات |
واجهة برمجة تطبيقات GraphQL لـ WordPress البرنامج المساعد يوفر خادم GraphQL لـ WordPress | - مصدر الرمز - كود منقول - ملاحظات |
إيجابيات وسلبيات Transpiling PHP
سبق وصف فائدة التحويل إلى PHP: فهي تسمح للكود المصدري باستخدام PHP 8.0 (أي أحدث إصدار من PHP) ، والذي سيتم تحويله إلى إصدار أقل لـ PHP للإنتاج ليتم تشغيله في تطبيق أو بيئة قديمة.
هذا يسمح لنا بشكل فعال بأن نصبح مطورين أفضل ، وننتج كودًا بجودة أعلى. هذا لأن كود المصدر الخاص بنا يمكن أن يستخدم أنواع اتحاد PHP 8.0 ، وخصائص PHP 7.4 المكتوبة ، والأنواع المختلفة والأنواع الزائفة المضافة إلى كل إصدار جديد من PHP ( mixed
من PHP 8.0 ، object
من PHP 7.2) ، من بين الميزات الحديثة الأخرى لـ PHP.
باستخدام هذه الميزات ، يمكننا اكتشاف الأخطاء بشكل أفضل أثناء التطوير وكتابة التعليمات البرمجية التي يسهل قراءتها.
الآن ، دعونا نلقي نظرة على العيوب.
يجب ترميزها وصيانتها
يمكن لـ Rector تحويل الشفرة تلقائيًا ، ولكن من المحتمل أن تتطلب العملية بعض الإدخال اليدوي لجعلها تعمل مع الإعداد المحدد لدينا.
يجب أيضًا نقل مكتبات الطرف الثالث
تصبح هذه مشكلة كلما أدى تحويلها إلى أخطاء حيث يتعين علينا بعد ذلك الخوض في كود المصدر الخاص بهم لمعرفة السبب المحتمل. إذا كان من الممكن إصلاح المشكلة وكان المشروع مفتوح المصدر ، فسنحتاج إلى إرسال طلب سحب. إذا لم تكن المكتبة مفتوحة المصدر ، فقد نواجه عقبة في الطريق.
رئيس الجامعة لا يعلمنا عندما لا يمكن تحويل الرمز
إذا كانت التعليمات البرمجية المصدر تحتوي على سمات PHP 8.0 أو أي ميزة أخرى لا يمكن تحويلها ، فلا يمكننا المتابعة. ومع ذلك ، لن يتحقق Rector من هذا الشرط ، لذلك نحتاج إلى القيام بذلك يدويًا. قد لا تكون هذه مشكلة كبيرة فيما يتعلق بالشفرة المصدرية الخاصة بنا لأننا على دراية بها بالفعل ، ولكنها قد تصبح عقبة فيما يتعلق بالتبعية للأطراف الثالثة.
تستخدم معلومات التصحيح الشفرة المترجمة ، وليس كود المصدر
عندما يُصدر التطبيق رسالة خطأ مع تتبع المكدس في الإنتاج ، سيشير رقم السطر إلى الشفرة المنقولة. نحتاج إلى التحويل مرة أخرى من الشفرة المنقولة إلى الشفرة الأصلية للعثور على رقم السطر المقابل في شفرة المصدر.
يجب أيضًا أن تكون الشفرة المنقولة مسبوقة
يمكن أن يستخدم مشروعنا المنقول وبعض المكتبات الأخرى المثبتة أيضًا في بيئة الإنتاج نفس التبعية للجهة الخارجية. سيتم نقل تبعية الطرف الثالث هذه لمشروعنا والاحتفاظ بكود المصدر الأصلي للمكتبة الأخرى. وبالتالي ، يجب أن تكون النسخة المنقولة مسبوقة عبر PHP-Scoper أو Strauss أو أي أداة أخرى لتجنب التعارضات المحتملة.
يجب أن يحدث النقل أثناء التكامل المستمر (CI)
نظرًا لأن الشفرة المنقولة ستتجاوز بشكل طبيعي الكود المصدري ، فلا ينبغي لنا تشغيل عملية التحويل على أجهزة الكمبيوتر الخاصة بنا للتطوير ، وإلا فإننا سنخاطر بخلق آثار جانبية. يعد تشغيل العملية أثناء تشغيل CI أكثر ملاءمة (المزيد حول هذا أدناه).

كيفية تحويل لغة PHP
أولاً ، نحتاج إلى تثبيت Rector في مشروعنا من أجل التطوير:
composer require rector/rector --dev
نقوم بعد ذلك بإنشاء ملف تكوين rector.php
في الدليل الجذر للمشروع يحتوي على مجموعات القواعد المطلوبة. لخفض الكود من PHP 8.0 إلى 7.1 ، نستخدم هذا التكوين:
use Rector\Set\ValueObject\DowngradeSetList; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; return static function (ContainerConfigurator $containerConfigurator): void { $containerConfigurator->import(DowngradeSetList::PHP_80); $containerConfigurator->import(DowngradeSetList::PHP_74); $containerConfigurator->import(DowngradeSetList::PHP_73); $containerConfigurator->import(DowngradeSetList::PHP_72); };
للتأكد من تنفيذ العملية كما هو متوقع ، يمكننا تشغيل أمر process
Rector في الوضع الجاف ، وتمرير الموقع (المواقع) المراد معالجتها (في هذه الحالة ، جميع الملفات الموجودة ضمن المجلد src/
):
vendor/bin/rector process src --dry-run
لإجراء التحويل ، نقوم بتشغيل أمر process
Rector ، والذي سيعدل الملفات داخل موقعها الحالي:
vendor/bin/rector process src
يرجى ملاحظة: إذا قمنا بتشغيل rector process
في حواسيب التطوير الخاصة بنا ، فسيتم تحويل الكود المصدري في مكانه ، ضمن src/
. ومع ذلك ، نريد إنتاج الكود المحول في موقع مختلف حتى لا نتجاوز كود المصدر عند الرجوع إلى إصدار سابق من الكود. لهذا السبب ، فإن تشغيل العملية هو الأنسب أثناء التكامل المستمر.
تحسين عملية التحويل
لإنشاء تسليم transpiled للإنتاج ، يجب تحويل رمز الإنتاج فقط ؛ يمكن تخطي التعليمات البرمجية اللازمة فقط من أجل التطوير. هذا يعني أنه يمكننا تجنب نقل جميع الاختبارات (لكل من مشروعنا وتبعياته) وجميع التبعيات من أجل التنمية.
فيما يتعلق بالاختبارات ، سنعرف بالفعل مكان وجود الاختبارات الخاصة بمشروعنا - على سبيل المثال ، ضمن tests/
. يجب علينا أيضًا معرفة مكان تبعيات التبعيات - على سبيل المثال ، ضمن tests/
، test/
Test/
(للمكتبات المختلفة). بعد ذلك ، نطلب من Rector تخطي معالجة هذه المجلدات:
return static function (ContainerConfigurator $containerConfigurator): void { // ... $parameters->set(Option::SKIP, [ // Skip tests '*/tests/*', '*/test/*', '*/Test/*', ]); };
فيما يتعلق بالتبعية ، يعرف Composer أيها مخصص للتطوير ( require-dev
في composer.json
) وأيها مخصصة للإنتاج ( require
تلك قيد الإدخال).
لاسترداد مسارات كل تبعيات الإنتاج من Composer ، نقوم بتشغيل:
composer info --path --no-dev
سينتج هذا الأمر قائمة من التبعيات بأسمائها ومسارها ، على النحو التالي:
brain/cortex /Users/leo/GitHub/leoloso/PoP/vendor/brain/cortex composer/installers /Users/leo/GitHub/leoloso/PoP/vendor/composer/installers composer/semver /Users/leo/GitHub/leoloso/PoP/vendor/composer/semver guzzlehttp/guzzle /Users/leo/GitHub/leoloso/PoP/vendor/guzzlehttp/guzzle league/pipeline /Users/leo/GitHub/leoloso/PoP/vendor/league/pipeline
يمكننا استخراج جميع المسارات وإدخالها في أمر Rector ، والذي سيقوم بعد ذلك بمعالجة مجلد src/
الخاص بمشروعنا بالإضافة إلى تلك المجلدات التي تحتوي على جميع التبعيات للإنتاج:
$ paths="$(composer info --path --no-dev | cut -d' ' -f2- | sed 's/ //g' | tr '\n' ' ')" $ vendor/bin/rector process src $paths
يمكن أن يمنع التحسين الإضافي Rector من معالجة تلك التبعيات بالفعل باستخدام إصدار PHP الهدف. إذا تم ترميز مكتبة باستخدام PHP 7.1 (أو أي إصدار أدناه) ، فلا داعي لتحويلها إلى PHP 7.1.
لتحقيق ذلك ، يمكننا الحصول على قائمة المكتبات التي تتطلب PHP 7.2 وما فوق ومعالجتها فقط. سنحصل على أسماء كل هذه المكتبات من خلال أمر Composer why-not
، مثل هذا:
composer why-not php "7.1.*" | grep -o "\S*\/\S*"
نظرًا لأن هذا الأمر لا يعمل مع علامة --no-dev
، لتضمين التبعيات للإنتاج فقط ، نحتاج أولاً إلى إزالة التبعيات من أجل التطوير وإعادة إنشاء أداة التحميل التلقائي ، وتنفيذ الأمر ، ثم إضافتها مرة أخرى:
$ composer install --no-dev $ packages=$(composer why-not php "7.1.*" | grep -o "\S*\/\S*") $ composer install
يسترد أمر Composer's info --path
مسار الحزمة بهذا التنسيق:
# Executing this command $ composer info psr/cache --path # Produces this response: psr/cache /Users/leo/GitHub/leoloso/PoP/vendor/psr/cache
نقوم بتنفيذ هذا الأمر لجميع العناصر الموجودة في قائمتنا للحصول على جميع المسارات التي يجب تحويلها:
for package in $packages do path=$(composer info $package --path | cut -d' ' -f2-) paths="$paths $path" done
أخيرًا ، نقدم هذه القائمة إلى Rector (بالإضافة إلى src/
المجلد الخاص بالمشروع):
هل تحتاج إلى حل استضافة يمنحك ميزة تنافسية؟ لقد جعلك Kinsta مغطى بسرعة لا تصدق وأحدث مستويات الأمان والتوسع التلقائي. تحقق من خططنا
vendor/bin/rector process src $paths
المزالق التي يجب تجنبها عند Transpiling Code
يمكن اعتبار رمز Transpiling فنًا ، وغالبًا ما يتطلب تعديلات خاصة بالمشروع. دعونا نرى بعض المشاكل التي قد نواجهها.
لا تتم معالجة القواعد المقيدة دائمًا
القاعدة المتسلسلة هي عندما تحتاج القاعدة إلى تحويل الكود الناتج عن قاعدة سابقة.
على سبيل المثال ، تحتوي مكتبة symfony/cache
على هذا الرمز:
final class CacheItem implements ItemInterface { public function tag($tags): ItemInterface { // ... return $this; } }
عند التحويل من PHP 7.4 إلى 7.3 ، يجب أن تخضع tag
الوظيفة لتعديلين:
- يجب تحويل نوع الإرجاع
ItemInterface
أولاً إلىself
، بسبب القاعدةDowngradeCovariantReturnTypeRector
- يجب بعد ذلك إزالة نوع الإرجاع
self
، نظرًا لقاعدةDowngradeSelfTypeDeclarationRector
يجب أن تكون النتيجة النهائية هي هذه:
final class CacheItem implements ItemInterface { public function tag($tags) { // ... return $this; } }
ومع ذلك ، يقوم رئيس الجامعة بإخراج المرحلة المتوسطة فقط:
final class CacheItem implements ItemInterface { public function tag($tags): self { // ... return $this; } }
المشكلة هي أن Rector لا يمكنه دائمًا التحكم في الترتيب الذي يتم تطبيق القواعد به.
الحل هو تحديد القواعد المتسلسلة التي تركت دون معالجة ، وتنفيذ تشغيل رئيس الجامعة الجديد لتطبيقها.
لتحديد القواعد المتسلسلة ، نقوم بتشغيل Rector مرتين على الكود المصدري ، مثل هذا:
$ vendor/bin/rector process src $ vendor/bin/rector process src --dry-run
في المرة الأولى ، قمنا بتشغيل Rector كما هو متوقع ، لتنفيذ التحويل. في المرة الثانية ، نستخدم العلم --dry-run
لاكتشاف ما إذا كانت لا تزال هناك تغييرات يتعين إجراؤها. إذا كان هناك ، فسيخرج الأمر برمز خطأ ، وسيشير إخراج "الفرق" إلى القاعدة (القواعد) التي لا يزال من الممكن تطبيقها. قد يعني ذلك أن الجولة الأولى لم تكن كاملة ، مع عدم معالجة بعض القواعد المتسلسلة.

بمجرد تحديد القاعدة (أو القواعد) المتسلسلة غير المطبقة ، يمكننا بعد ذلك إنشاء ملف تكوين Rector آخر - على سبيل المثال ، rector-chained-rule.php
سينفذ القاعدة المفقودة. بدلاً من معالجة مجموعة كاملة من القواعد لجميع الملفات ضمن src/
، يمكننا هذه المرة تشغيل القاعدة المفقودة المحددة في الملف المحدد حيث يلزم تطبيقها:
// rector-chained-rule.php use Rector\Core\Configuration\Option; use Rector\DowngradePhp74\Rector\ClassMethod\DowngradeSelfTypeDeclarationRector; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; return static function (ContainerConfigurator $containerConfigurator): void { $services = $containerConfigurator->services(); $services->set(DowngradeSelfTypeDeclarationRector::class); $parameters = $containerConfigurator->parameters(); $parameters->set(Option::PATHS, [ __DIR__ . '/vendor/symfony/cache/CacheItem.php', ]); };
أخيرًا ، أخبرنا Rector في مساره الثاني باستخدام ملف التكوين الجديد عبر الإدخال --config
:
# First pass with all modifications $ vendor/bin/rector process src # Second pass to fix a specific problem $ vendor/bin/rector process --config=rector-chained-rule.php
قد تكون تبعيات المؤلف غير متسقة
يمكن للمكتبات أن تعلن عن تبعية للتطوير (على سبيل المثال ، تحت require-dev
في composer.json
) ، ومع ذلك ، قم بالإشارة إلى بعض الأكواد منها للإنتاج (على سبيل المثال في بعض الملفات ضمن src/
، وليس في tests/
).
عادة ، هذه ليست مشكلة لأنه قد لا يتم تحميل هذا الرمز في الإنتاج ، لذلك لن يكون هناك خطأ في التطبيق. ومع ذلك ، عندما يعالج Rector الكود المصدري وتبعياته ، فإنه يتحقق من أنه يمكن تحميل جميع التعليمات البرمجية المرجعية. سيرمي Rector خطأ إذا أشار أي ملف إلى جزء من التعليمات البرمجية من مكتبة غير مثبتة (لأنه تم الإعلان عن أنه ضروري للتطوير فقط).
على سبيل المثال ، تطبق فئة EarlyExpirationHandler
من مكون ذاكرة التخزين المؤقت في Symfony واجهة MessageHandlerInterface
من مكون Messenger:
class EarlyExpirationHandler implements MessageHandlerInterface { //... }
ومع ذلك ، يعلن symfony symfony/cache
أن symfony/messenger
تبعية للتطوير. بعد ذلك ، عند تشغيل Rector في مشروع يعتمد على symfony/cache
، سيظهر خطأ:
[ERROR] Could not process "vendor/symfony/cache/Messenger/EarlyExpirationHandler.php" file, due to: "Analyze error: "Class Symfony\Component\Messenger\Handler\MessageHandlerInterface not found.". Include your files in "$parameters->set(Option::AUTOLOAD_PATHS, [...]);" in "rector.php" config. See https://github.com/rectorphp/rector#configuration".
هناك ثلاثة حلول لهذه المشكلة:
- في تكوين Rector ، تخطي معالجة الملف الذي يشير إلى هذا الجزء من التعليمات البرمجية:
return static function (ContainerConfigurator $containerConfigurator): void { // ... $parameters->set(Option::SKIP, [ __DIR__ . '/vendor/symfony/cache/Messenger/EarlyExpirationHandler.php', ]); };
- قم بتنزيل المكتبة المفقودة وأضف مسارها ليتم تحميله تلقائيًا بواسطة Rector:
return static function (ContainerConfigurator $containerConfigurator): void { // ... $parameters->set(Option::AUTOLOAD_PATHS, [ __DIR__ . '/vendor/symfony/messenger', ]); };
- اجعل مشروعك يعتمد على المكتبة المفقودة للإنتاج:
composer require symfony/messenger
النقل والتكامل المستمر
كما ذكرنا سابقًا ، في أجهزة الكمبيوتر الخاصة بنا للتطوير ، يجب علينا استخدام علامة --dry-run
عند تشغيل Rector ، أو خلاف ذلك ، سيتم تجاوز الكود المصدري بالشفرة المترجمة. لهذا السبب ، من الأنسب تشغيل عملية التحويل الفعلي أثناء التكامل المستمر (CI) ، حيث يمكننا تدوير العدائين المؤقتين لتنفيذ العملية.
الوقت المثالي لتنفيذ عملية التحويل هو عند إنشاء الإصدار لمشروعنا. على سبيل المثال ، الكود أدناه هو سير عمل لـ GitHub Actions ، والذي ينشئ إصدارًا من مكون WordPress الإضافي:
name: Generate Installable Plugin and Upload as Release Asset on: release: types: [published] jobs: build: name: Build, Downgrade and Upload Release runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/[email protected] - name: Downgrade code for production (to PHP 7.1) run: | composer install vendor/bin/rector process sed -i 's/Requires PHP: 7.4/Requires PHP: 7.1/' graphql-api.php - name: Build project for production run: | composer install --no-dev --optimize-autoloader mkdir build - name: Create artifact uses: montudor/[email protected] with: args: zip -X -r build/graphql-api.zip . -x *.git* node_modules/\* .* "*/\.*" CODE_OF_CONDUCT.md CONTRIBUTING.md ISSUE_TEMPLATE.md PULL_REQUEST_TEMPLATE.md rector.php *.dist composer.* dev-helpers** build** - name: Upload artifact uses: actions/[email protected] with: name: graphql-api path: build/graphql-api.zip - name: Upload to release uses: JasonEtco/[email protected] with: args: build/graphql-api.zip application/zip env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
يحتوي سير العمل هذا على إجراء قياسي لإصدار مكون إضافي لبرنامج WordPress عبر إجراءات GitHub. الإضافة الجديدة ، لتحويل كود البرنامج المساعد من PHP 7.4 إلى 7.1 ، تحدث في هذه الخطوة:
- name: Downgrade code for production (to PHP 7.1) run: | vendor/bin/rector process sed -i 's/Requires PHP: 7.4/Requires PHP: 7.1/' graphql-api.php
عند أخذها معًا ، يقوم سير العمل هذا الآن بتنفيذ الخطوات التالية:
- للتحقق من الكود المصدري لمكوِّن WordPress الإضافي من مستودعه ، والمكتوب باستخدام PHP 7.4
- تثبيت تبعيات الملحن الخاصة به
- ينقل الكود الخاص به من PHP 7.4 إلى 7.1
- يعدل إدخال "يتطلب PHP" في رأس الملف الرئيسي للمكون الإضافي من
"7.4"
إلى"7.1"
- يزيل التبعيات اللازمة للتنمية
- ينشئ ملف .zip للمكوِّن الإضافي ، باستثناء جميع الملفات غير الضرورية
- تحميل ملف .zip كأصل إصدار (بالإضافة إلى كونه عنصرًا أساسيًا في GitHub Action)
اختبار الشفرة المنقولة
بمجرد تحويل الكود إلى PHP 7.1 ، كيف نعرف أنه يعمل بشكل جيد؟ أو بعبارة أخرى ، كيف نعرف أنه قد تم تحويله بالكامل ، ولم يتم ترك أي بقايا من إصدارات أعلى من كود PHP وراءنا؟
على غرار تحويل الشفرة ، يمكننا تنفيذ الحل ضمن عملية CI. الفكرة هي إعداد بيئة العداء باستخدام PHP 7.1 وتشغيل linter على الشفرة المنقولة. إذا كان أي جزء من التعليمات البرمجية غير متوافق مع PHP 7.1 (مثل خاصية مكتوبة من PHP 7.4 لم يتم تحويلها) ، فسيظهر خطأ في linter.
وسيط PHP الذي يعمل بشكل جيد هو PHP Parallel Lint. يمكننا تثبيت هذه المكتبة كتبعية للتطوير في مشروعنا ، أو تثبيت عملية CI كمشروع مؤلف مستقل:
composer create-project php-parallel-lint/php-parallel-lint
متى احتوت الشفرة على PHP 7.2 وما فوق ، فإن PHP Parallel Lint ستعرض خطأ مثل هذا:
Run php-parallel-lint/parallel-lint layers/ vendor/ --exclude vendor/symfony/polyfill-ctype/bootstrap80.php --exclude vendor/symfony/polyfill-intl-grapheme/bootstrap80.php --exclude vendor/symfony/polyfill-intl-idn/bootstrap80.php --exclude vendor/symfony/polyfill-intl-normalizer/bootstrap80.php --exclude vendor/symfony/polyfill-mbstring/bootstrap80.php PHP 7.1.33 | 10 parallel jobs ............................................................ 60/2870 (2 %) ............................................................ 120/2870 (4 %) ... ............................................................ 660/2870 (22 %) .............X.............................................. 720/2870 (25 %) ............................................................ 780/2870 (27 %) ... ............................................................ 2820/2870 (98 %) .................................................. 2870/2870 (100 %) Checked 2870 files in 15.4 seconds Syntax error found in 1 file ------------------------------------------------------------ Parse error: layers/GraphQLAPIForWP/plugins/graphql-api-for-wp/graphql-api.php:55 53| '0.8.0', 54| \__('GraphQL API for WordPress', 'graphql-api'), > 55| ))) { 56| $plugin->setup(); 57| } Unexpected ')' in layers/GraphQLAPIForWP/plugins/graphql-api-for-wp/graphql-api.php on line 55 Error: Process completed with exit code 1.
دعنا نضيف اللنتر إلى سير عمل CI الخاص بنا. خطوات التنفيذ لتحويل الشفرة من PHP 8.0 إلى 7.1 واختبارها هي:
- تحقق من شفرة المصدر
- اجعل البيئة تقوم بتشغيل PHP 8.0 ، حتى يتمكن Rector من تفسير الكود المصدري
- انقل الكود إلى PHP 7.1
- قم بتثبيت أداة PHP linter
- قم بتبديل إصدار PHP الخاص بالبيئة إلى 7.1
- قم بتشغيل linter على التعليمات البرمجية المنقولة
يقوم سير عمل GitHub Action بالمهمة:
name: Downgrade PHP tests jobs: main: name: Downgrade code to PHP 7.1 via Rector, and execute tests runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/[email protected] - name: Set-up PHP uses: shivammathur/[email protected] with: php-version: 8.0 coverage: none - name: Local packages - Downgrade PHP code via Rector run: | composer install vendor/bin/rector process # Prepare for testing on PHP 7.1 - name: Install PHP Parallel Lint run: composer create-project php-parallel-lint/php-parallel-lint --ansi - name: Switch to PHP 7.1 uses: shivammathur/[email protected] with: php-version: 7.1 coverage: none # Lint the transpiled code - name: Run PHP Parallel Lint on PHP 7.1 run: php-parallel-lint/parallel-lint src/ vendor/ --exclude vendor/symfony/polyfill-ctype/bootstrap80.php --exclude vendor/symfony/polyfill-intl-grapheme/bootstrap80.php --exclude vendor/symfony/polyfill-intl-idn/bootstrap80.php --exclude vendor/symfony/polyfill-intl-normalizer/bootstrap80.php --exclude vendor/symfony/polyfill-mbstring/bootstrap80.php
يرجى ملاحظة أنه يجب استبعاد العديد من ملفات bootstrap80.php
من مكتبات polyfill الخاصة بـ Symfony (والتي لا يلزم تحويلها) من linter. تحتوي هذه الملفات على PHP 8.0 ، لذلك قد يتسبب linter في حدوث أخطاء عند معالجتها. ومع ذلك ، فإن استبعاد هذه الملفات يعد آمنًا حيث سيتم تحميلها على الإنتاج فقط عند تشغيل PHP 8.0 أو أعلى:
if (\PHP_VERSION_ID >= 80000) { return require __DIR__.'/bootstrap80.php'; }
ملخص
علمتنا هذه المقالة كيفية تحويل كود PHP الخاص بنا ، مما سمح لنا باستخدام PHP 8.0 في كود المصدر وإنشاء إصدار يعمل على PHP 7.1. يتم النقل عبر Rector ، وهي أداة إعادة بناء PHP.
إن تحويل الشفرة يجعلنا مطورين أفضل حيث يمكننا بشكل أفضل اكتشاف الأخطاء في التطوير وإنتاج كود يسهل قراءته وفهمه بشكل طبيعي.
يتيح لنا Transpiling أيضًا فصل الكود الخاص بنا بمتطلبات PHP المحددة عن CMS. يمكننا الآن القيام بذلك إذا كنا نرغب في استخدام أحدث إصدار من PHP لإنشاء مكون WordPress الإضافي أو وحدة Drupal المتاحة للجمهور دون تقييد شديد لقاعدة المستخدمين الخاصة بنا.
هل لديك أي أسئلة متبقية حول ترجمة PHP؟ دعنا نعلم فى قسم التعليقات!