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

بمجرد تصحيح مشروعي في PHP / Laravel ، رأيت هذا الخطأ في المتصفح:

كان هذا ، على الأقل ، غريبًا ، لأنه بناءً على الوصف الوارد في RFC 2616 ، يعني خطأ 502 أن "الخادم ، الذي يعمل كبوابة أو وكيل ، تلقى استجابة غير صحيحة من الخادم الرئيسي." في حالتي ، لم تكن هناك بوابات ، ولم يكن هناك وكيل بين خادم الويب والمتصفح ، وكان خادم الويب nginx يعمل تحت Virtualbox ، ويقدم محتوى الويب مباشرة ، دون أي وسطاء. تحتوي سجلات nginx على ما يلي:
2018/06/20 13:42:41 [error] 2791#2791: *2206 recv() failed (104: Connection reset by peer) while reading response header from upstream, client: 192.168.10.1, server: colg.test, request: "GET / HTTP/1.1", upstream: "fastcgi://unix:/var/run/php/php7.1-fpm.sock:", host: "colg.test"
اقترحت الكلمتان "upstream server" في وصف الخطأ 502 ("upstream server" في النسخة الإنجليزية الأصلية من RFC) بعض خوادم الشبكة الإضافية على مسار الطلب من المتصفح إلى nginx ، ولكن ، في هذه الحالة ، على ما يبدو ، تلك المذكورة في الرسالة وحدة PHP-FPM ، كونها برنامج خادم ، تعمل كخادم أساسي للغاية. في سجلات PHP ، كان هذا:
[20-Jun-2018 13:42:41] WARNING: [pool www] child 26098 exited on signal 11 (SIGSEGV - core dumped) after 102247.908379 seconds from start
الآن أصبح من الواضح أين ظهرت المشكلة ، ولكن سببها غير واضح. سقطت PHP للتو في التفريغ الأساسي ، ولم تعرض أي معلومات حول النقطة التي حدث فيها خطأ في تفسير برنامج PHP. لذا فقد حان الوقت للقبض على أسد في الصحراء - لاستخدام طريقي المفضل لتصحيح الأخطاء عن طريق الانقسام في مثل هذه الحالات. توقعًا للاعتراضات في التعليقات ، ألاحظ أنه يمكن للمرء استخدام مصحح أخطاء هنا ، على سبيل المثال ، نفس XDebug ، لكن الانقسام كان أكثر إثارة للاهتمام. بالإضافة إلى ذلك ، سيأتي الدور إلى XDebug.
لذلك ، في طريقة معالجة طلب الويب ، قمت بتعيين أبسط إخراج تشخيصي ، مع مزيد من إكمال البرنامج ، للتأكد من عدم حدوث أي خطأ في مكان تثبيته:
echo “I am here”; die();
الآن تبدو الصفحة السيئة كما يلي:

بعد وضع الأمر المكتوب أعلاه ، أولاً في البداية ، ثم في نهاية مسار معالجة طلب الويب ، اكتشفت أن خطأ (من يشك في ذلك!) يحدث في مكان ما بين هاتين النقطتين. بعد تعيين التشخيص في منتصف مسار طلب الويب ، اكتشفت أن الخطأ يظهر في مكان ما بالقرب من النهاية. بعد زوجين من هذه التكرارات ، أدركت أن الخطأ لا يحدث في وحدة تحكم هندسة Laravel MVC نفسها ، ولكن عند الخروج منه بالفعل ، عند عرض العرض ، وهو أبسط هنا ، بهذه الروح:
@extends('layouts.app') @section('content') <div> <div class="panel-heading">Myservice</div> <div class="panel-body"></div> </div> @endsection
كما ترى ، لا يحتوي قالب العرض على كود PHP (محرك قالب Laravel يسمح لك باستخدام كود PHP في العرض) ، وبالتأكيد ليست المشاكل هنا. ولكن أعلاه نرى أن هذا العرض يرث قالب appouts.app ، لذا ابحث هناك. الأمر أكثر تعقيدًا بالفعل: هناك عناصر التنقل ونماذج تسجيل الدخول وأشياء أخرى مشتركة في جميع صفحات الخدمة. بإهمال كل ما هو موجود ، سأعطي فقط خطًا ، ونتيجة لذلك نشأ الفشل ، تم العثور على نفس الانقسام. هنا هو الخط:
<script> window.bkConst = {!! (new App\Src\Helpers\UtilsHelper())->loadBackendConstantsAsJSData() !!}; </script>
هنا ، فقط في كود قالب العرض ، تم استخدام PHP. لقد كان "سحري" - اشتقاق الثوابت الخلفية ، في شكل كود JS ، لاستخدامها في الواجهة ، باسم مبدأ DRY. يسرد أسلوب loadBackendConstantsAsJSData عدة فئات مع الثوابت الضرورية في الواجهة الأمامية. حدث الخطأ في طريقة addClassConstants التي يستخدمها ، حيث تم استخدام الاستبطان PHP للحصول على قائمة بثوابت الصنف:
private function addClassConstants(string $classFullName, array &$constantsArray) { $r = new ReflectionClass($classFullName); $result = []; $className = $r->getShortName(); $classConstants = $r->getConstants(); foreach($classConstants as $name => $value) { if (is_array($value) || is_object($value)) { continue; } $result["$className::$name"] = $value; } $constantsArray = array_merge($constantsArray, $result); }
بعد البحث بين الفئات مع الثوابت التي تم تمريرها إلى هذه الطريقة ، اتضح أن سبب كل شيء - هذه الفئة مع الثوابت - هو الطريق إلى طرق REST API.
class APIPath { const API_BASE_PATH = '/api/v1'; const DATA_API = self::API_BASE_PATH . "/data"; ... const DATA_ADDITIONAL_API = DATA_API . "/additional"; }
هناك عدد قليل من الأسطر فيه ، ولإيجاد الخط الصحيح ، كان الانقسام مفيدًا مرة أخرى. الآن ، آمل أن يلاحظ الجميع أن الذات :: مفقودة في تعريف الثابت أمام الاسم الثابت DATA_API. بعد إضافته إلى مكانه الصحيح ، عمل كل شيء.
بعد أن قررت أن المشكلة تكمن في آلية الاستبطان ، بدأت في كتابة مثال بسيط لإعادة إنتاج خطأ:
class SomeConstants { const SOME_CONSTANT = SOME_NONSENSE; } $r = new \ReflectionClass(SomeConstants::class); $r->getConstants();
ومع ذلك ، عند تشغيل هذا البرنامج النصي ، لم يكن PHP يتعطل ، ولكنه أصدر تحذيرًا سليمًا تمامًا.
PHP Warning: Use of undefined constant SOME_NONSENSE - assumed 'SOME_NONSENSE' (this will throw an Error in a future version of PHP) in /home/vagrant/code/colg/_tmp/1.php on line 17
عند هذه النقطة ، كنت مقتنعًا بالفعل بأن المشكلة تتجلى ليس فقط عند تحميل الموقع ، ولكن أيضًا عند تنفيذ الرمز المكتوب أعلاه من خلال سطر الأوامر. كان الفرق الوحيد بين وقت التشغيل والحد الأدنى من النص البرمجي هو وجود سياق Laravel: تم تشغيل رمز المشكلة من خلال فائدته الحرفية. لذلك كان هناك نوع من الاختلاف في ظل Laravel. لفهم ما هو عليه ، حان الوقت لاستخدام المصحح. عند تشغيل التعليمات البرمجية تحت xdebug ، رأيت أن العطل يحدث بعد استدعاء أسلوب ReflectionClass :: getConstants في أسلوب Illuminate \ Foundation \ Bootstrap \ HandleExceptions :: handleError ، والذي يبدو بسيطًا جدًا:
public function handleError($level, $message, $file = '', $line = 0, $context = []) { if (error_reporting() & $level) { throw new ErrorException($message, 0, $level, $file, $line); } }
وصل خيط التنفيذ إلى هناك بعد طرح استثناء بسبب الخطأ في وصف الثابت الذي بدأ منه كل شيء ، وتعطل PHP عند محاولة رمي ErrorException. استثناء في معالج الاستثناء ... تذكرت على الفور
الخطأ المزدوج الشهير. لذلك ، لإحداث فشل ، تحتاج إلى تثبيت معالجات الاستثناء المشابهة لـ Laravel. أعلى قليلاً في الكود كان مجرد طريقة التمهيد التي فعلت ذلك:
الآن ، بدا الحد الأدنى النهائي كما يلي:
<?php class SomeConstants { const SOME_CONSTANT = SOME_NONSENSE; } function handleError() { throw new ErrorException(); } set_error_handler('handleError'); set_exception_handler('handleError'); $r = new \ReflectionClass(SomeConstants::class); $r->getConstants();
وإطلاقه حشد بثبات مترجم PHP الإصدار 7.2.4 في تفريغ الأساسية.
يبدو أن هناك عودية لا حصر لها هنا - عند معالجة استثناء من الخطأ الأصلي ، يتم طرح الاستثناء التالي في handleException ، ومعالجته مرة أخرى في handleException ، وهكذا إلى ما لا نهاية. علاوة على ذلك ، لإعادة إنتاج الفشل ، تحتاج إلى تعيين كل من معالج_الخطأ وخطأ الاستثناء ، إذا تم تعيين واحد منهما فقط ، فلن تحدث المشكلة. فشلت أيضًا في طرح استثناء ، بدلاً من إلقاء خطأ ، يبدو أن هذا ليس تكرارًا عاديًا تمامًا ، ولكنه شيء مثل التبعية الدائرية.
بعد ذلك ، تحققت من وجود مشكلة في إصدارات مختلفة من PHP (شكرًا Docker!). اتضح أن الفشل يتجلى فقط ، بدءًا من إصدار PHP 7.1 ، تعمل الإصدارات السابقة من PHP بشكل صحيح - يقسمون على استثناء ErrorException غير المكتشف.
ما هي الاستنتاجات التي يمكن استخلاصها من كل هذا؟
- التصحيح عن طريق الانقسام ، على الرغم من أنه طريقة قديمة لتصحيح الأخطاء ، ولكن قد يكون ذلك ضروريًا في بعض الأحيان ، خاصة في ظروف نقص المعلومات التشخيصية
- في رأيي ، أخطاء 502 غير مفهومة ، سواء الرسالة حولها ("بوابة سيئة") وفك تشفيرها في RFC حول "الاستجابة غير الصحيحة من الخادم الرئيسي". على الرغم من ذلك ، إذا كنت تعتبر الوحدات المتصلة بخادم الويب كبرامج خادم ، يمكنك فهم معنى فك تشفير الخطأ في RFC. ومع ذلك ، لنفترض أن نفس PHP-FPM في الوثائق تسمى الوحدة النمطية وليس الخادم.
- يدفع محلل ثابت ، فإنه سيبلغ على الفور عن خطأ في وصف الثابت. ولكن بعد ذلك لن يتم القبض على الخلل.
واسمحوا لي أن أنهي هذا ، شكرا لكم جميعا على اهتمامكم!
Bagreport -
أرسلت .
UPD: تم
إصلاح الخلل. إذا حكمنا من خلال التعليمات البرمجية ، فقد انتهى الأمر في آلية التفكير - في معالجة الخطأ لأسلوب ReflectionClass :: getConstants