NoVerify: linter PHP يعمل بسرعة

هناك أدوات تحليل ثابتة جيدة لـ PHP: PHPStan ، Psalm ، Phan ، Exakat. ينتشر Linters بعملهم بشكل جيد ، ولكن ببطء شديد ، لأن جميعهم تقريبًا مكتوب بلغة PHP (أو Java). بالنسبة للاستخدام الشخصي أو المشروع الصغير ، يعد هذا أمرًا طبيعيًا ، ولكن بالنسبة إلى موقع به ملايين المستخدمين ، يعد هذا عاملاً بالغ الأهمية. يبطئ اللبط البطيء خط أنابيب CI ويجعل من المستحيل استخدامه كحل يمكن دمجه في محرر نص أو IDE.



موقع مع ملايين المستخدمين هو فكونتاكتي. تطوير وإضافة وظائف جديدة واختبار وإصلاح الأخطاء والمراجعات - كل هذا يجب أن يتم بسرعة ، في ظروف المواعيد النهائية الصعبة. لذلك ، يعد اللتر الجيد والسريع الذي يمكنه التحقق من قاعدة الشفرة لـ 5 ملايين سطر في 5-10 ثوانٍ أمرًا لا غنى عنه. 

لا توجد فضلات مناسبة في السوق ، لذلك يوري نصرتدينوف (انت صخرة) من فكونتاكتي كتب مساعدته لفرق التطوير - NoVerify. هذا linter لـ PHP ، وهو مكتوب في Go. يعمل بسرعة 10-30 مرة أسرع من نظرائه ، يمكنه العثور على شيء لا يحذر منه PhpStorm ، يمتد بسهولة ويدمج جيدًا في المشاريع التي لم تسمع عن التحليل الثابت من قبل. 

سوف يخبر اسكندر شاريبوف عن هذا اللتر . تحت القطع: كيف اختاروا اللتر وفضلوا كتابة ما لديهم ، ولماذا يكون NoVerify سريعًا للغاية وكيف يتم ترتيبه من الداخل ، ولماذا تم كتابته في Go ، وما الذي يمكن أن يجده وكيف يتوسع ، وما هي الحلول التي كان عليك القيام بها من أجله وما يمكن بناؤه على أساسه.


اسكندر شاريبوف (quasilyte) يعمل في البنية التحتية للواجهة الخلفية لـ VKontakte وهو على دراية جيدة بـ NoVerify. في الماضي ، كان يعمل في مترجم Go في فريق Intel. لا يكتب بلغة PHP ، لكن هذه هي لغته المفضلة للتحليل الثابت - لديها الكثير من الأشياء التي يمكن أن تسوء.

ملحوظة. لفهم الخلفية ، اقرأ مقال يوري نصرتدينوف ، مؤلف كتاب NoVerify on Habré ، مع خلفية ومقارنة مع بعض النسالة الموجودة ، والتي عادة ما تكون مكتوبة بلغة PHP. جميع التصريحات في اتجاه PHP (في مقال يوري وهنا) هي مزحة. إسكندر يحب PHP ، والجميع يحب PHP.

تطوير المنتج


في فكونتاكتي ، هذا تطوير موقع على KPHP. السرعة مهمة لـ فكونتاكتي: إصلاح الأخطاء وإضافة وظائف جديدة وتطويرها من المرحلة الأولى إلى الأخيرة. لكن السرعة مصحوبة بأخطاء ، خاصة عندما تكون هناك مواعيد نهائية صعبة - فنحن في عجلة من أمرنا وعصبية ونرتكب أخطاء أكثر مما هو في وضع هادئ.

تؤثر الأخطاء على المستخدمين . نحن لا نريدهم أن يعانوا ، وبالتالي فإننا نسيطر على الجودة. لكن مراقبة الجودة تبطئ التنمية . هذا أيضًا لا نريده ، لذلك يجب تقليل التأثير.

للقيام بذلك ، يمكننا إجراء المزيد من مراجعات الكود دون فشل ، وتوظيف المزيد من المختبرينوكتابة المزيد من الاختبارات. لكن كل هذا غير مؤتمت بشكل سيئ: يجب أن تتم المراجعة ، ويجب كتابة الاختبارات.

المهام الرئيسية لفريقي مختلفة.

جمع المقاييس وتحليلها وإصلاحها بسرعة . إذا حدث خطأ ما ، فنحن نريد استعادته بسرعة ، وفهم ما هو الخطأ وإصلاحه وإضافة رمز العمل بسرعة إلى الإنتاج.

راقب صرامة خط الأنابيب حتى لا يدخل رمز عدم العمل في الإنتاج على الإطلاق - لست بحاجة إلى دحرجه. هنا تأتي الوبر إلى الإنقاذ - محلل الكود الثابت. سنتحدث عن هذا.

اختر اللنت


دعنا نختار اللنتر الذي نضيفه إلى خط الأنابيب. نتخذ نهجًا بسيطًا - نقوم بصياغة المتطلبات.

يجب أن يعمل Linter بسرعة . هناك العديد من الخطوات في خط الأنابيب الخاص بنا: يجب ألا يستغرق تشغيل اللتر الكثير من الوقت والمطور الذي يستغرق وقتًا طويلاً ، بينما ينتظر التعليقات.

دعم الشيكات "الخاصة بهم" . على الأرجح ، ليس لدى اللبيدة كل ما نحتاجه - سيتعين علينا إضافة الشيكات الخاصة بنا. يجب أن يجدوا مشاكل نموذجية لقاعدة الكود الخاصة بنا ، والتحقق من الكود من وجهة نظر مشروعنا. لا يمكن تغطية كل هذا (أو بشكل ملائم) من خلال الاختبارات.

دعم الشيكات "الخاصة". يمكننا كتابة العديد من الاختبارات ، ولكن هل سيتم دعمها جيدًا؟ على سبيل المثال ، إذا كتبنا على التعبيرات العادية ، فستصبح أكثر تعقيدًا عندما تحتاج إلى مراعاة سياق اللغة ودلالاتها ونحوها. لذلك ، الاختبارات ليست خيارًا.

كُتبت معظم الوبر التي قمنا بمراجعتها بلغة PHP. لكنهم لا يمررون عند الطلب. Linters في PHP (لا يوجد تصنيف AOT حتى الآن) يعمل بشكل أبطأ 10-20 مرة من اللغات الأخرى - يمكن تحليل أكبر ملف لدينا لعشرات الثواني. هذا يبطئ سير العمل أكثر من اللازم - وهذا عيب قاتل . ماذا يفعل المطورون في هذه الحالة؟ يكتبون بأنفسهم.

لذلك ، قمنا بكتابة منتج NoVerify PHP linter على Go. لماذا عليه؟ المفسد: ليس فقط لأن جورا قررت ذلك.

Noverify


Go هو حل وسط جيد بين سرعة التطوير والإنتاجية.
أول "دليل" في الصورة مع "الرسوم البيانية": سرعة تنفيذ جيدة ، ودعم سهل. نخسر في سرعة التطور ، لكن النقطتين الأوليين أكثر أهمية بالنسبة لنا.


الأرقام مأخوذة من الرأس ، ولا يدعمها شيء.

بالنسبة إلى "الدليل" الثاني ، جادل بشكل أكثر بساطة.


PHP أبطأ ، Go أسرع ، وهكذا. 

لقد اخترنا Go لثلاثة أسباب.

Go كلغة للمرافق من السهل تعلمها على المستوى الأساسي . في فريق تطوير PHP ، بالتأكيد ، سمع شخص ما عن Go ، ونظر إلى Docker ، يعرف أنه مكتوب في Go ، وربما رأى المصدر. مع الفهم الأساسي ، بعد أسبوع أو أسبوعين من التعلم المكثف Go ، سيكونون قادرين على كتابة التعليمات البرمجية عليه.

الذهاب فعال جدا . حتى المبتدئ لن يتمكن من ارتكاب العديد من الأخطاء ، لأن Go لديه ضبط جيد والكثير من اللتر. في Go ، يكون متوسط ​​الرمز أفضل قليلاً من اللغات الأخرى ، نظرًا لوجود عدد أقل بكثير من الطرق لتصويب ساقك.

تطبيقات Go سهلة الصيانة.Go هي لغة برمجة ناضجة إلى حد ما حيث تتوفر تقريبًا جميع أدوات المطورين التي قد ترغب فيها.

سوف نتحقق من NoVerify مع متطلباتنا.

  • NoVerify أسرع عدة مرات من البدائل .

  • لذلك ، يمكنك كتابة ملحقات ، مفتوحة المصدر ، وتلك الخاصة بك. من المهم أن نفصل هذه الشيكات ، ويمكنك كتابة الشيكات الخاصة بك.
  • من السهل اختبار وتطوير. جزئيًا ، لأن التوزيع القياسي لـ Go له إطار عمل قياسي مع التنميط والاختبار. يتم استخدامه بشكل رئيسي لاختبار الوحدة. يمكن استخدام البراعة بشكل خاص للتكامل ، كما نفعل - لدينا اختبار تكامل مكتوب من خلال اختبارات Go.

تسوية التكامل


لنبدأ بالمشكلة. عندما تبدأ أي لنتر لأول مرة في مشروع قديم لم يستخدم أي تحليل ، فمن المحتمل أن ترى هذا.



يا رمز بلدي! لا أحد سيصحح الكثير من الأخطاء. أريد إغلاق المشروع وإزالة اللتر وعدم تشغيله مرة أخرى. ماذا تفعل لتجنب هذا؟

دمج


تشغيل في وضع فرق . لا نريد إجراء جميع عمليات الفحص على المشروع بالكامل مع وجود مليون خطأ في كل خطوة من خطوات CI. ربما تعرف عن خط الأساس: في NoVerify ، هذا خارج الصندوق ، لا تحتاج إلى تضمين أداة مساعدة منفصلة. اعتبرنا على الفور أن هناك حاجة إلى مثل هذا النظام.

إضافة إرث (بائعون) إلى الاستثناءات . من الأسهل عدم لمس بعض الأشياء ، وتركها جانباً حتى مع وجود عيب ، حتى لا تعدلها بنفسك ولا تترك أثراً في التاريخ.

نبدأ بمجموعة فرعية من الشيكات . لا يمكنك ربط كل شيء يتضمن النمط. بادئ ذي بدء ، سيجد أخطاء حقيقية: سنعثر على شيء جديد ونصلحه وننتقل إليه.

نجمع ملاحظات من الزملاء. كيف تفهم متى يحين وقت تشغيل شيء آخر؟ اسأل الزملاء. بمجرد سعادتهم باختفاء الأخطاء وعدم العثور على أي شيء تقريبًا ، قم بتشغيل شيء آخر - حان وقت العمل.

إعداد Git


يعني وضع Diff أن لديك نظام التحكم في الإصدار - Git. إذا كان لديك SVN ، فلن تساعد التعليمات ، انتقل إلى Git.

نقوم بتثبيت خطاف الدفع المسبق مع اللنتر ونتحقق قبل أن نبدأ الرمز. نتحقق من الجهاز المحلي مع خيار --no-verifyلتجاوز اللنت . قد يكون من الملائم استخدام خطاف ما قبل الاستلام وتعطيل اللنتر من جانب الخادم ، ولكن لأسباب تاريخية ، تحدث الكثير من الأشياء في VK في خطاف ما قبل الدفع ، لذلك تم بناء NoVerify هناك أيضًا.

بعد الدفع ، يتم إطلاق فحوصات CI. يحتوي NoVerify على وضعين للعمل: مع التحليل الكامل وبدونه. على CI ، أنت على الأرجح تريد (ويمكن) تشغيل--git-full-diff- يمكن تحميل الأجهزة في CI بشكل أقوى والتحقق حتى من الملفات التي لم تتغير. على الأجهزة المحلية ، يمكننا تشغيل تحليل أقل صرامة ، ولكن أسرع للملفات التي تم تغييرها فقط (أسرع من 5 إلى 15 ثانية). 

ايجابيات مزيفة




ضع في اعتبارك المثال أدناه: في هذه الوظيفة ، يتم قبول شيء يحتوي على حقل ، ولكن النوع غير موصوف بأي شكل من الأشكال. ليس هناك حقيقة أن هناك مجالًا على الإطلاق عندما يتم استدعاء دالة من سياقات مختلفة. في نسخة صارمة ، يمكن للذئب أن يشكو: "ليس من الواضح ما هو نوعه ، كيف يمكنني إرجاع حقل بدون شيكات؟" لكن هذا ليس بالضرورة خطأ.

function get_foo($obj) {
    return $obj->foo;
    ^^^
}

Warning:
Property "foo" does not exist

التدخل الإيجابي الكاذب. 
هذا هو السبب الرئيسي للتخلي عن اللنت. يختار الأشخاص خيارات أخرى تجد عددًا أقل من الأخطاء ، لكنهم ينتجون نتائج إيجابية خاطئة أقل.

غالبًا ما يندفعون عندما يعمل شيء ما ، لكن هذا ليس خطأ. لدى الكثير آلية لتجاوز اللنت - للتشغيل مع العلم دون التحقق من اللنت. في بلدنا ، كان هذا العلم يسمى no-verifyخطاف ما قبل الدفع. كنا نستخدمه في كثير من الأحيان وتم تخليد الاسم باسم اللنت.

الانتقائية


خاصية اللنتر أخرى. على سبيل المثال ، كثير لا يفهم الأسماء المستعارة. في PHP ، sizeofهذا تناظري count: لا يحسب الحجم ، ولكنه يُرجع عدد العناصر. النموذج العقلي للمطورين C له sizeofمعنى مختلف. إذا كان هناك على sizeofالأرجح في قاعدة التعليمات البرمجية ، يعني count. ولكن هذا هو انتقاء.

$len = sizeof($x);
    ^^^^^^

Warning:
use "count" instead of "sizeof"

ماذا نفعل معها؟


كن صارمًا وقويًا لحكم كل شيء دون استثناء . لفرض القواعد والمطالبة والمراقبة وعدم السماح لها بالتحايل - لا تعمل أبداً. لكي تعمل هذه الصلابة ، يجب أن يتكون الفريق من نفس الأشخاص: الشخصية ، ومستوى الثقافة ، والمشاة ، وإدراك جودة الشفرة. إذا لم يكن الأمر كذلك ، فستكون هناك أعمال شغب. تجميع فريق من استنساخك أسهل من إجبارهم على اتباع جميع القواعد. 

لا تحجب دفع / الالتزام على تعليقات مثل تحديد sizeofيوم count. على الأرجح ، هذا ليس خطأ ، ولكنه اختيار ولا يؤثر على الشفرة. ولكن بعد ذلك سيتم تجاهل 99٪ من الردود (من قبل الفريق) وسيكون هناك دائمًا ردود إضافية في الشفرة sizeof.

السماح بمستوى معين من التكوين لفرق ومطورين مختلفين.يمكنك تكوين التكوين عن كل أمر حتى أولئك الذين لا يريدون التغيير sizeofل countلا تستطيع أن تفعل هذا. دع الجميع يتبع القواعد. خيار جيد ، لكن الاتساق سيتدهور ، وفي بعض الدلائل سيكون الرمز أسوأ قليلاً.

قم بإجراء هذه الفحوصات مرة واحدة في الشهر على subbotniks . لا يمكن تشغيل الشيكات في كل مرة على CI أو خطاف ما قبل الدفع ، ولكن في روتين Cron مرة واحدة في الشهر. قم بتشغيل وتحرير كل شيء تجده بعد التطوير النشط. لكن هذا العمل يتطلب موارد للأتمتة والتحقق.

لا تفعل شيئا. يعد إيقاف تشغيل الشيكات الأسلوبية أحد الخيارات أيضًا.

مرونة




سيكون هناك دائمًا حل وسط بين المطور السعيد والليتر السعيد. من السهل جعل اللابتن سعيدًا: الوضع الأكثر صرامة ونقص الحلول. ربما بعد ذلك لن يبقى أحد في الفريق ، لذلك إذا تدخل اللنتر في العمل ، فهذه مشكلة.
عمل مفيد فوق كل شيء.

التحقق من التفاصيل الفنية


الشيكات الخاصة فكونتاكتي. كتب Noverify شيء من هذا القبيل. في GitHub ، ينقسم مستودع NoVerify إلى قسمين: إطار العمل المستخدم لتنفيذ اللتر ، والفحوصات المنفصلة ، vklints . يتم ذلك بحيث يقوم اللنتر بتحميل فحوصات الطرف الثالث: يمكنك كتابة وحدة منفصلة على Go ويقومون بتسجيل أنفسهم في الإطار. بعد البدء من NoVerify binary ، يقوم إطار العمل بتحميل جميع مجموعات الشيكات المسجلة ويعملون ككل. 



NoVerify هي مكتبة وثنائي (linter).

تسمى الشيكات لدينا vklints . لقد وجدوا أنهم لا يرون PhpStorm و Open Source NoVerify - أخطاء مهمة غير مناسبة للاستخدام العام.

ما هو فكلينتس؟

التحقق من تفاصيل استخدام بعض الوظائف والفئات وحتى المتغيرات العالمية التي لا تتبع اصطلاحاتنا. هذا شيء لا يمكن استخدامه في أماكن خاصة لأسباب مختلفة موصوفة في دليل الأسلوب.

اختبارات نمط إضافية. لا تتوافق مع ما هو مقبول في مجتمع PHP ، ولا يتم وصفها في توصية معيار PHP أو حتى تتعارض معها ، ولكن بالنسبة لنا فهي المعيار. ليس هناك جدوى من إضافتها إلى المصدر المفتوح لأنك لا تريد متابعتها.

متطلبات المقارنة الصارمة لبعض الأنواع . على سبيل المثال ، لدينا فحص يتطلب مقارنة السلاسل مع عامل المقارنة ===. على وجه الخصوص ، يتطلب الأمر تمرير إشارة لمقارنة صارمة للوظائف من أجل مقارنة السلاسل.

مفاتيح الصفيف المشبوهة.خطأ آخر مثير للاهتمام: في بعض الأحيان ، عندما يلتزم المطورون ، يمكنهم الضغط على مجموعات المفاتيح قبل حفظ الملف. أحيانًا ما تبقى هذه الأحرف في سلسلة أو في جزء من التعليمات البرمجية. مرة واحدة ، في مفتاح الصفيف كان الحرف الروسي "Y". على الأرجح ، ضغط المطور على CTRL-S في التخطيط الروسي ، وحفظ الملف والتزامه. في بعض الأحيان نجد مثل هذه المفاتيح في المصفوفات ، ولكن الأخطاء الجديدة لن تنتهي.

القواعد الديناميكية هي آلية تمديد NoVerify أبسط الموضحة في PHP. تمت كتابة مقالة منفصلة حول هذا: كيفية إضافة الشيكات إلى NoVerify دون كتابة سطر واحد من كود Go .

كيف يعمل NoVerify


لتحليل PHP تحتاج إلى محلل . لا يمكننا استخدام محلل PHP في PHP: إنه بطيء ، من Go يمكن استخدامه فقط من خلال غلاف في C. لذلك ، نستخدم المحلل في Go.

لدى هذا المحلل مشاكل عديدة. لسوء الحظ ، يمكنه فقط العمل مع UTF-8 ونحن بحاجة إلى التمييز بين UTF-8 وليس UTF-8 . بالإضافة إلى UTF-8 ، غالبًا ما يوجد Windows-1251 في مشاريع PHP الروسية. لدينا أيضا مثل هذه الملفات. كيف نتعرف عليهم؟ يسرد

الملف encodings.xmlجميع المسارات التي توجد فيها الملفات ذات UTF-8. وإذا واجهتنا ملف خارج هذه المسارات، ثم على الطاير نحن تيار إلى UTF-8 من تدفق تيار (بدون تحويل مقدما).


التحليل والتحليل


اكتمل في بضع خطوات. في البداية - نقوم بتحميل البيانات الوصفية من phpstorm-stubs . هذه بيانات تشبه رمز PHP ، ولكن لا يتم تنفيذها مطلقًا وتصف أنواع المدخلات / المخرجات من الوظائف القياسية. تحتوي البيانات الوصفية phpStorm على توجيه تجاوز مفيد لل Linter ... فهي تسمح لنا بوصف ، على سبيل المثال ، أننا نقبل مصفوفة من النوع ونعيد T[]النوع (مفيد للوظائف array_pop).


يتم تحميل بذرة Phpstorm أولاً. نستخدم البيانات الوصفية كمعلومات النوع الأولي - الأساس. يمتص اللتر هذا الأساس ونبدأ في تحليل المصادر.

نقوم بتحميل السيد الحالي قبل الامتصاص. نتحقق من الرمز في وضعين:

  • التغييرات المحلية : فيما يتعلق بخط الأساس نجد أخطاء جديدة في الكود ؛
  • نشير إلى نطاق المراجعات : المراجعات الأولى والأخيرة ، وبينهم كل شيء شامل - هذا هو الرمز الجديد ، وكل شيء "قبل" قديم.

بعد ذلك تأتي مرحلة التحليل.



تحليل AST . لدينا الآن بيانات وصفية ، اكتب المعلومات. نأخذ كل مصدر PHP ، parsim والتحليل مباشرة فوق AST - ليس لدينا تمثيل وسيط في الوقت الحالي. تحليل AST الخام ليس مناسبًا للغاية ، خاصة إذا كنت تعتمد على المكتبات وأنواع البيانات التي تمثلها. 



يتم تخزين نتائج التحليل في ذاكرة التخزين المؤقت . يتم استخدامه في إعادة التحليل ، وهو أسرع بكثير.

التقارير والتصفية . ثم ننشئ تقارير أو تحذيرات مرتين : أولاً نجد تحذيرات للنسخة القديمة من الشفرة (قبل خط الأساس) ، ثم للنسخة الجديدة. يتم تصفية التقارير عن طريق المقارنة (فرق) - نبحث عن التحذيرات التي ظهرت في الإصدار الجديد من الكود ونمررها إلى المستخدم. في بعض المحللين الساكنين ، يسمى هذا "الوضع الأساسي".



تحليل الشفرة المزدوجة (في وضع الاختلاف) بطيء جدًا. ولكن يمكننا تحمله - لا يزال NoVerify أسرع عشرات المرات من روابط PHP الأخرى. في الوقت نفسه ، لديها احتياطي لتسريع إضافي ، على الأقل بنسبة 30 في المائة.

كيف نحلل الملفات؟ في PHP ، يمكنك استدعاء دالة قبل تعريفها - تحتاج إلى معرفة المعلومات حول هذه الوظيفة قبل تحليلها. لذلك ، نبدأ أولاً في الملف بأكمله في AST ، والفهرس ، وتحديد أنواع جميع الوظائف ، وتسجيل الفئات ، ثم تحليلها فقط. 



التحليل هو الممر الثاني من خلال الملف . يعمل معظم المترجمين والمترجمين مع مرورين وأكثر. لكي لا "تفحص" الملف مرة ثانية ، يجب أن يكون لديك إعلانات قبل الاستخدام ، كما هو الحال في C ، على سبيل المثال.

استدلال النوع


الجزء الأكثر إثارة للاهتمام هو أن الأخطاء غالبًا ما تتم مواجهتها هنا. لا يزال لا يتوافق مع صحة نظام نوع PHP ، والذي يصعب تعريفه رسميًا.

كيف يبدو النموذج.


النموذج الدلالي (تجريبي).

أنواع الأنواع:

  • المتوقع هو ما نصفه في التعليقات ، ونتوقع بعض الأنواع في البرنامج ، لكن هذا لا يعني أنها تستخدم فيه حقًا.
  • الفعلي - الحقيقي الموجود في البرنامج. على سبيل المثال ، إذا قمنا بتعيين رقم لشيء ما ، فمن الواضح أنه intأو float(إذا كان هذا الرقم عائمًا) سيكون أنواعًا فعلية. 

تبدو الأنواع الفعلية "أقوى" - فهي حقيقية وصحيحة. ولكن في بعض الأحيان لا يمكننا الحصول على نوع إلا من خلال التعليق التوضيحي.

الشروح (في أنواع المتوقعة) يمكن تقسيمها إلى فئتين: الثقة و عدم الثقة . على سبيل المثال ، تنتمي phpstorm-stubs إلى الفئة الأولى. تعتبر معتدلة (بدون أخطاء) قبل استخدامها. تلك التي لا يمكن الاعتماد عليها هي تلك التي يكتبها المطورون الآخرون ، لأنهم قد يكون لديهم أخطاء.

يمكن أيضًا تقسيم الأنواع الفعلية إلى عدة أجزاء: القيم والتأكيد والمسندات ونوع التلميح ، مما يوسع قدرات PHP 7. ولكن هناك مشكلة لا يحلها نوع التلميح.

المتوقع مقابل الفعلي


لنفترض أن الصف Fooلديه ورث. من فئة السليل ، يمكننا استدعاء طرق غير موجودة في Foo ، لأن السليل يمتد الأصل. ولكن إذا حصلنا على الوريث Fooمن new static()خلال هذا التعليق التوضيحي لنوع الإرجاع (من self) ، فستظهر مشكلة. يمكننا استدعاء هذه الطريقة ، ولكن IDE لن يطالبك - تحتاج إلى تحديد static(). هذا هو رابط ثابت متأخر في PHP ، عندما لا يمكن Fooلورث الصف العودة

class Foo {
    /** @return static */
    public function newStatic() : self {
        return new static();
    }
}
// actual = Foo
// expected = static

عندما نكتب new static()، لا يمكن أن يعود الفصل فقط new Foo. على سبيل المثال ، إذا كان Fooالفصل الدراسي موروثًا منه bar، فقد يكون هناك new bar. وفقًا لذلك ، نحتاج إلى نوعين من المعلومات على الأقل. لا أحد منهم فائض - كلاهما مطلوب.

لذلك ، سيكون النوع الفعلي هنا self- لمترجم PHP. ولكن لكي يعمل IDE والوبر ، نحن بحاجة static. إذا قمنا باستدعاء هذا الرمز من سياق فئة الوريث ، فنحن بحاجة إلى معرفة المعلومات التي تفيد بأن هذه ليست نفس الفئة الأساسية ولديها طرق أكثر.

class Foo {
    /** @return static */
    public function newStatic() : self {
        return new static();
    }
}
// actual -  PHP 
// expected -   IDE/

اكتب تلميحًا


الكتابة الثابتة والتلميح بالنوع ليسا نفس الشيء.
ربما سمعت أنه يمكنك التحقق فقط من حدود الوظائف. على الحدود ، نتحقق من المدخلات والمخرجات ، حيث يكون المدخل هو وسيطات الدالة. داخل الوظيفة ، يمكنك القيام بأي هراء: تعيين fooقيمة int، على الرغم من أنك وصفت ما هي T. يمكنك أن تشكو من أنك تنتهك الأنواع التي أعلنتها ، ولكن بالنسبة لـ PHP لا يوجد خطأ

declare(strict_types=1);
    function f(T $foo) {
        $foo = 10; //  int
        return $foo;
}

مثال أصعب - نعود foo؟ في بداية الوظيفة ، قررنا fooأنه كان كذلك T، ولا توجد معلومات حول الإرجاع. 

declare(strict_types=1);
function f(T $foo) {
    $foo = 10; //  int
    return $foo;
}
// ? 1. f -> int
// ? 2. f -> T|int
// ? 3. f -> T

أي نوع صحيح؟ الأولين ، سنقوم بتحليل الفرق بينهما. PhpStorm و linter إخراج الخيار الثاني. على الرغم من حقيقة أنها تعود دائمًا int، T|intيتم استنتاج النوع - "اتحاد" الأنواع. هذا نوع يمكن تعيينه لكل من هاتين القيمتين: أولاً كان لدينا معلومات حول النوع T ، ثم قمنا بتعيينه 10، لذلك fooيجب أن يكون نوع المتغير متوافقًا مع هذين النوعين.

شروح


قد تكمن التعليقات والشروح.
في المثال أدناه ، كتبنا أننا نعيد رقمًا ، لكننا نعيد سلسلة. إذا كان اللنتر يعمل فقط على مستوى التعليقات التوضيحية ونوع التلميح ، فإننا نعتبر أنه يعود دائمًا int. لكن الأنواع الفعلية تساعد فقط في الابتعاد عن هذا: هنا ، النوع المتوقع هو هذا int، والنوع الفعلي هو سلسلة. يعلم Linter أنه تم إرجاع السلسلة وقد يحذرك من أنك وعدت بالعودة int. هذا الفصل مهم بالنسبة لنا.

/** @return int */
function f() { return "I lied!"; }

وراثة الشروح. هنا ، أعني أن فئة تطبق نوعًا من الواجهة لديها طريقة. تحتوي الطريقة على تعليقات ووثائق وأنواع جيدة - وهي ضرورية لتنفيذ الواجهة. ولكن لا توجد تعليقات في التنفيذ: لا يوجد سوى @inheritdocشيء أو لا شيء على الإطلاق.

interface IFoo {
    /** @return int */
    public function foo();
}
class Fooer implements IFoo {
    /** @inheritdoc */
    public function foo() { return "10"; }
}

ما الذي يعيد هذه الطريقة؟ يبدو أن ما تم وصفه في الواجهة - يتم إرجاعه int، ولكن في الواقع سلسلة. هذا ليس جيدًا: PHP هو نفس الشيء ، ولكن التقارب مهم بالنسبة لنا.

هناك خياران لإصلاح هذا الرمز. من الواضح أن العودةint . ولكن ربما تحتاج إلى إرجاع نوع مختلف. ماذا أفعل؟ اكتب أننا نعيد السلسلة . في هذه الحالة ، تكون معلومات النوع الصريح ضرورية لكل من IDE والليتر من أجل تحليل الشفرة بشكل صحيح.

interface IFoo {
    /** @return int */
    public function foo();
}
class Fooer implements IFoo {
    /** @return string */
    public function foo() { return "10"; }
}

لن تكون هذه المعلومات مطلوبة على الإطلاق إذا كتب الناس تعليقات ، لا @inheritdoc. ليس من الضروري أن يفهم PhpStorm الأنواع التي لديك. ولكن إذا لم يتم وصف الأنواع بشكل صحيح ، فستكون هناك مشكلة.

يحتوي كل من PhpStorm و linter على مجموعات من الأخطاء المتقطعة عندما نستخدم نفس الملفات للبيانات الوصفية (الأنواع). إذا قمنا بإصلاح كل ما نحتاجه في دعامات phpstorm من مستودع JetBrains ، فمن المرجح أن ينكسر IDE. إذا تركت كل شيء بشكل افتراضي ، فلن يعمل كل شيء بشكل صحيح

بالنسبة لنا. لذلك ، لدينا شوكة صغيرة -  VKCOM / phpstorm-stubs . تمت إضافة بضع تصحيحات لإصلاح شيء غير مناسب. لا يمكنني أن أوصي به لـ PhpStorm ، لكن من الضروري أن يعمل اللنتر.

المصدر المفتوح


Noverify هو مشروع مفتوح المصدر. يتم نشره على GitHub .

تعليمات موجزة "إذا حدث خطأ ما."

إذا كان هناك شيء مكسور أو لا يبدأ. رد الفعل الخاطئ هو الاستياء وإزالة NoVerify. رد الفعل الصحيح: إصدار تذكرة على GitHub والتحدث عن مشكلتك. على الأرجح ، سيتم حلها في يوم أو يومين.

تنقصك بعض الميزات. رد فعل خاطئ: قم بإزالة NoVerify واكتب اللنت الخاص بك (على الرغم من أن كتابة اللنت الخاص بك رائع دائمًا). رد الفعل الصحيح: لإصدار تذكرة على GitHub ، وربما نضيف وظيفة جديدة. إنها أكثر تعقيدًا بالميزات من الأخطاء - تنشأ مناقشة ، ولدى كل شخص رؤية مختلفة للتنفيذ في الفريق. لكن في النهاية ، لا تزال قيد التنفيذ.

إذا كنت مهتمًا بتطوير المشروع أو إذا كنت تريد فقط التحدث عن التحليل الثابت ، فانتقل إلى غرفة الدردشة الخاصة بنا - noverify_linter .

PHP-, , , , PHP Russia.

, . , , . telegram- @PHPRussiaConfChannel. , .

Source: https://habr.com/ru/post/undefined/


All Articles