PHP للمبتدئين. خطأ في التعامل

الصورة

الشخص الوحيد الذي لا يفعل شيئًا لا يرتكب الأخطاء ، ونحن مثال على ذلك - نجلس ونعمل بلا كلل ، اقرأ Habr :)

في هذه المقالة ، سوف أقود قصتي حول الأخطاء في PHP ، وكيفية كبحها.

أخطاء


أصناف في عائلة الخطأ


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

لمنع حدوث خطأ واحد من دون أن يلاحظها أحد ، يجب تمكين تتبع جميع الأخطاء باستخدام دالة error_reporting () ، واستخدام توجيه display_errors لتمكين عرضه:

<?php error_reporting(E_ALL); ini_set('display_errors', 1); 

أخطاء قاتلة


أكثر أنواع الأخطاء رهيبة هي القاتلة ، ويمكن أن تحدث أثناء التجميع وأثناء عمل المحلل اللغوي أو PHP ، أثناء مقاطعة البرنامج النصي.

E_PARSE

يظهر هذا الخطأ عند ارتكاب خطأ في بناء الجملة الإجمالي ، ولا يفهم مترجم PHP ما تريده منه ، على سبيل المثال ، إذا لم تغلق مجعد أو قوس:

 <?php /** * Parse error: syntax error, unexpected end of file */ { 

أو كتبوا بلغة غير مفهومة:

 <?php /** * Parse error: syntax error, unexpected '...' (T_STRING) */     

تحدث أيضًا دعامات إضافية ، وليست ذات أهمية أو دائرية أو مجعدتين:

 <?php /** * Parse error: syntax error, unexpected '}' */ } 

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

 <?php //     error_reporting(E_ALL); ini_set('display_errors', 1); // ..     

E_ERROR

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

لم يتم العثور على المكون الإضافي:

 /** * Fatal error: require_once(): Failed opening required 'not-exists.php' * (include_path='.:/usr/share/php:/usr/share/pear') */ require_once 'not-exists.php'; 

تم طرح استثناء (أي نوع من الوحش ، سأخبرك به لاحقًا) ، لكن لم تتم معالجته:

 /** * Fatal error: Uncaught exception 'Exception' */ throw new Exception(); 

عند محاولة استدعاء طريقة فئة غير موجودة:

 /** * Fatal error: Call to undefined method stdClass::notExists() */ $stdClass = new stdClass(); $stdClass->notExists(); 

نقص الذاكرة الخالية (أكثر مما هو موصوف في توجيه memory_limit ) أو شيء آخر مشابه:

 /** * Fatal Error: Allowed Memory Size */ $arr = array(); while (true) { $arr[] = str_pad(' ', 1024); } 

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

 /** * Fatal error: Maximum function nesting level of '256' reached, aborting! */ function deep() { deep(); } deep(); 

ليس قاتلا


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

تحذير

غالبًا ما يحدث عند توصيل ملف باستخدام include ، لكنه لا يظهر على الخادم أو ارتكبت خطأ يشير إلى المسار إلى الملف:

 /** * Warning: include_once(): Failed opening 'not-exists.php' for inclusion */ include_once 'not-exists.php'; 

يحدث ذلك إذا استخدمت نوعًا خاطئًا من الوسائط عند استدعاء الوظائف:

 /** * Warning: join(): Invalid arguments passed */ join('string', 'string'); 

هناك الكثير منهم ، وقائمة كل شيء لا معنى له ...

E_NOTICE

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

عند الوصول إلى متغير غير محدد:

 /** * Notice: Undefined variable: a */ echo $a; 

عند الوصول إلى عنصر صفيف غير موجود:

 /** * Notice: Undefined index: a */ $b = []; $b['a']; 

عند الوصول إلى ثابت غير موجود:

 /** * Notice: Use of undefined constant UNKNOWN_CONSTANT - assumed 'UNKNOWN_CONSTANT' */ echo UNKNOWN_CONSTANT; 

عندما لا يتم تحويل أنواع البيانات:

 /** * Notice: Array to string conversion */ echo array(); 

لتجنب مثل هذه الأخطاء - كن حذرًا ، وإذا أخبرك IDE بشيء ، فلا تتجاهله:

PHP E_NOTICE في PHPS العاصفة

E_STRICT

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

 /** * Strict standards: Non-static method Strict::test() should not be called statically */ class Strict { public function test() { echo "Test"; } } Strict::test(); 


هذا النوع من الأخطاء مناسب لإصدار PHP 5.6 ، وتقريباً جميعها قد تم قطعها
7 مباريات. اقرأ المزيد في RFC ذات الصلة . إذا كان أي شخص يعرف أين بقيت هذه الأخطاء ، فاكتب في التعليقات


E_DEPRECATED

لذا ، سوف يقسم PHP إذا كنت تستخدم وظائف قديمة (أي تلك التي تم تمييزها على أنها مهجورة ، ولن تكون في الإصدار الرئيسي التالي):

 /** * Deprecated: Function split() is deprecated */ //  ,   PHP 7.0 //    PHP 5.3 split(',', 'a,b'); 

في محرري ، سيتم شطب وظائف مماثلة:

PHP E_DEPRECATED في PHPS العاصفة

مخصص


هذا النوع ، الذي يطوره مطور الكود بنفسه ، لم أره منذ فترة طويلة ، ولا أوصي بإساءة معاملتك:

  • E_USER_ERROR - خطأ فادح
  • E_USER_WARNING - ليس خطأ فادح
  • E_USER_NOTICE - رسائل ليست أخطاء

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

 /** * @deprecated Deprecated since version 1.2, to be removed in 2.0 */ function generateToken() { trigger_error('Function `generateToken` is deprecated, use class `Token` instead', E_USER_DEPRECATED); // ... // code ... // ... } 

الآن بعد أن أصبحت على دراية بمعظم أنواع الأخطاء وأنواعها ، فقد حان الوقت للتعبير عن شرح مختصر حول تشغيل توجيهات العرض - العرض:

  • إذا كان display_errors = on ، في حالة حدوث خطأ ، سيتلقى المستعرض html مع نص الخطأ والرمز 200
  • إذا كان display_errors = off ، فعند الأخطاء الفادحة ، سيكون رمز الاستجابة 500 ولن يتم إرجاع النتيجة إلى المستخدم ، للأخطاء الأخرى - لن تعمل الشفرة بشكل صحيح ، ولكنها لن تخبر أي شخص عنها


ترويض


هناك 3 وظائف للتعامل مع الأخطاء في PHP:

  • set_error_handler () - يعين معالج الأخطاء التي لا تقاطع البرنامج النصي (أي عن الأخطاء غير المميتة)
  • error_get_last () - يحصل على معلومات حول الخطأ الأخير
  • register_shutdown_function () - يسجل معالجًا سيتم تشغيله عند انتهاء البرنامج النصي. لا تنطبق هذه الوظيفة مباشرة على معالجات الأخطاء ، ولكن غالبًا ما يتم استخدامها لهذا الغرض.

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

  • $errno - تحتوي الوسيطة الأولى على نوع الخطأ كعدد صحيح
  • $errstr - تحتوي الوسيطة الثانية على رسالة خطأ
  • $errfile - وسيطة ثالثة اختيارية تحتوي على اسم الملف الذي حدث فيه الخطأ
  • $errline - وسيطة رابعة اختيارية تحتوي على رقم السطر الذي حدث فيه الخطأ
  • $errcontext - تحتوي الوسيطة الخامسة الاختيارية على مجموعة من جميع المتغيرات الموجودة في النطاق الذي حدث فيه الخطأ

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

 <?php //    ,  E_NOTICE error_reporting(E_ALL & ~E_NOTICE); ini_set('display_errors', 1); //    function myHandler($level, $message, $file, $line, $context) { //         switch ($level) { case E_WARNING: $type = 'Warning'; break; case E_NOTICE: $type = 'Notice'; break; default; //   E_WARNING   E_NOTICE //      //      PHP return false; } //    echo "<h2>$type: $message</h2>"; echo "<p><strong>File</strong>: $file:$line</p>"; echo "<p><strong>Context</strong>: $". join(', $', array_keys($context))."</p>"; // ,    ,      return true; } //   ,         set_error_handler('myHandler', E_ALL); 

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

 function shutdown() { echo '    '; } register_shutdown_function('shutdown'); 

هذه الوظيفة ستعمل دائما!

لكن بالعودة إلى الأخطاء ، لتتبع ظهور الخطأ في رمز الخطأ ، نستخدم دالة error_get_last () ، وبمساعدتها يمكنك الحصول على معلومات حول آخر خطأ تم اكتشافه ، وبما أن الأخطاء القاتلة تقاطع تنفيذ الشفرة ، فإنها ستلعب دائمًا دور "الأخير":

 function shutdown() { $error = error_get_last(); if ( //       is_array($error) && //       in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR]) ) { //    (       ) while (ob_get_level()) { ob_end_clean(); } //    echo "    ,  "; } } register_shutdown_function('shutdown'); 

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

عن الشراهة


لنقم باختبار بسيط ومعرفة عدد الموارد الثمينة التي يتناولها الخطأ الأكثر تافهة:

 /** *      */ //     $time= microtime(true); define('AAA', 'AAA'); $arr = []; for ($i = 0; $i < 10000; $i++) { $arr[AAA] = $i; } printf('%f seconds <br/>', microtime(true) - $time); 

نتيجة لتشغيل هذا البرنامج النصي ، حصلت على هذه النتيجة:

 0.002867 seconds 

أضف الآن الخطأ في الحلقة:

 /** *     */ //     $time= microtime(true); $arr = []; for ($i = 0; $i < 10000; $i++) { $arr[BBB] = $i; //   ,      } printf('%f seconds <br/>', microtime(true) - $time); 

النتيجة أسوأ بشكل متوقع ، وترتيب الحجم (حتى طلبي الحجم!):

 0.263645 seconds 

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

أين دفن الكلب


PHP له رمز خاص "@" - عامل قمع الأخطاء ، يتم استخدامه من أجل عدم كتابة معالجة الأخطاء ، ولكن يعتمد على السلوك الصحيح لـ PHP في هذه الحالة:

 <?php echo @UNKNOWN_CONSTANT; 

في هذه الحالة ، لا يزال يتم استدعاء معالج الأخطاء المحدد في set_error_handler() ، ويمكن تتبع حقيقة تطبيق القمع على الخطأ عن طريق استدعاء دالة error_reporting() داخل المعالج ، وفي هذه الحالة سيعود 0 .
إذا قمت بقمع الأخطاء بهذه الطريقة ، فإن هذا يقلل من الحمل على المعالج مقارنة بما إذا كنت تخفيها فقط (انظر الاختبار المقارن أعلاه) ، ولكن على أي حال ، فإن قمع الأخطاء أمر شرير
المهمة
تحقق من كيفية تأثير كبح الخطأ مع @ على مثال الحلقة السابقة.

استثناءات


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

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

الاستثناء هو كائن من فئة Exception أو أحد أحفاده العديدة ، ويحتوي على نص الخطأ ، والحالة ، وقد يحتوي أيضًا على ارتباط إلى استثناء آخر أصبح السبب الرئيسي لذلك. نموذج الاستثناء في PHP يشبه النموذج المستخدم في لغات البرمجة الأخرى. يمكن طرح استثناء (كما يقولون ، "رمي") باستخدام مشغل throw ، ويمكنك التقاط ("صيد") بيان catch . يجب أن تكون الكود الذي يطرح الاستثناء محاطًا بلوك try أجل الحصول على الاستثناء. يجب أن تحتوي كل مجموعة catch على catch مطابق واحد على الأقل أو كتلة أخيرة:

 try { //      if (random_int(0, 1)) { throw new Exception("One"); } echo "Zero" } catch (Exception $e) { //      echo $e->getMessage(); } 

في هذه الحالات ، يجدر استخدام الاستثناءات

  • إذا كان هناك العديد من العمليات التي قد تفشل في إطار طريقة / وظيفة واحدة
  • إذا أعلن إطارك أو مكتبتك عن استخدامها

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

 $directory = __DIR__ . DIRECTORY_SEPARATOR . 'logs'; //     if (!is_dir($directory)) { throw new Exception('Directory `logs` is not exists'); } //         if (!is_writable($directory)) { throw new Exception('Directory `logs` is not writable'); } //  -   ,      if (!$file = @fopen($directory . DIRECTORY_SEPARATOR . date('Ym-d') . '.log', 'a+')) { throw new Exception('System can\'t create log file'); } fputs($file, date("[H:i:s]") . " done\n"); fclose($file); 

وفقًا لذلك ، سوف نلاحظ هذه الاستثناءات مثل:

 try { //      // ... } catch (Exception $e) { //    echo " : ". $e->getMessage(); } 

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

 //    class FileSystemException extends Exception {} //     class DirectoryException extends FileSystemException { //   const DIRECTORY_NOT_EXISTS = 1; const DIRECTORY_NOT_WRITABLE = 2; } //     class FileException extends FileSystemException {} 

الآن ، إذا استخدمت هذه الاستثناءات ، يمكنك الحصول على الكود التالي:

 try { //      if (!is_dir($directory)) { throw new DirectoryException('Directory `logs` is not exists', DirectoryException::DIRECTORY_NOT_EXISTS); } if (!is_writable($directory)) { throw new DirectoryException('Directory `logs` is not writable', DirectoryException::DIRECTORY_NOT_WRITABLE); } if (!$file = @fopen($directory . DIRECTORY_SEPARATOR . date('Ym-d') . '.log', 'a+')) { throw new FileException('System can\'t open log file'); } fputs($file, date("[H:i:s]") . " done\n"); fclose($file); } catch (DirectoryException $e) { echo "   : ". $e->getMessage(); } catch (FileException $e) { echo "   : ". $e->getMessage(); } catch (FileSystemException $e) { echo "  : ". $e->getMessage(); } catch (Exception $e) { echo " : ". $e->getMessage(); } 

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

لذا ، ماذا سيحدث إذا لم تقم بالاستثناء؟ سوف تحصل على "خطأ فادح: استثناء غير معروف ...". غير سارة.

لتجنب هذا الموقف ، يجب عليك استخدام الدالة set_exception_handler () وتعيين المعالج للاستثناءات التي يتم طرحها خارج كتلة try-catch ولم تتم معالجتها. بعد استدعاء مثل هذا المعالج ، سيتم إيقاف تنفيذ البرنامج النصي:

 //     //     set_exception_handler(function($exception) { /** @var Exception $exception */ echo $exception->getMessage(), "<br/>\n"; echo $exception->getFile(), ':', $exception->getLine(), "<br/>\n"; echo $exception->getTraceAsString(), "<br/>\n"; }); 

سوف أخبرك أيضًا بالبناء باستخدام الكتلة النهائية - سيتم تنفيذ هذه الكتلة بغض النظر عما إذا كان قد تم طرح استثناء أم لا:

 try { //      } catch (Exception $e) { //      //     } finally { // ,       } 

لفهم ما يعطينا هذا ، سأقدم المثال التالي لاستخدام الكتلة الأخيرة:

 try { // -    //     $handler = mysqli_connect('localhost', 'root', '', 'test'); try { //        // ... throw new Exception('DB error'); } catch (Exception $e) { //  ,     //     ,    throw new Exception('Catch exception', 0, $e); } finally { // ,      //      finally mysqli_close($handler); } //     ,       echo "Ok"; } catch (Exception $e) { //  ,    echo $e->getMessage(); echo "<br/>"; //      echo $e->getPrevious()->getMessage(); } 

أي تذكر - سيتم تنفيذ الحظر finally حتى إذا رميت استثناءًا في كتلة catch (في الحقيقة ، هذا ما كان ينوي).

للحصول على مقال تمهيدي من المعلومات فقط الحق ، الذي يتوق لمزيد من التفاصيل ، ستجدها في المادة رمز استثنائي ؛)

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

PHP7 - كل شيء ليس كما كان من قبل


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

  1. عندما تحدث أخطاء فادحة من النوع E_ERRORأو أخطاء فادحة مع إمكانية معالجة E_RECOVERABLE_ERRORPHP يلقي استثناء
  2. هذه الاستثناءات لا ترث فئة الاستثناء (تذكر ، تحدثت عن التوافق مع الإصدارات السابقة ، كل هذا من أجلها)
  3. ترث هذه الاستثناءات فئة الخطأ
  4. تطبق كل من فصول الاستثناء والخطأ واجهة Throwable
  5. لا يمكنك تطبيق واجهة قابلة للرمي في التعليمات البرمجية الخاصة بك

Throwableتكررنا الواجهة بالكامل تقريبًا Exception:

 interface Throwable { public function getMessage(): string; public function getCode(): int; public function getFile(): string; public function getLine(): int; public function getTrace(): array; public function getTraceAsString(): string; public function getPrevious(): Throwable; public function __toString(): string; } 

هل هو صعب؟ الآن للحصول على أمثلة ، خذ تلك التي كانت أعلى وتحديث قليلاً:

 try { // ,     include 'e_parse_include.php'; } catch (Error $e) { var_dump($e); } 

نتيجة لذلك ، نلاحظ الخطأ ونطبع:

 object(ParseError)#1 (7) { ["message":protected] => string(48) "syntax error, unexpected '' (T_STRING)" ["string":"Error":private] => string(0) "" ["code":protected] => int(0) ["file":protected] => string(49) "/www/education/error/e_parse_include.php" ["line":protected] => int(4) ["trace":"Error":private] => array(0) { } ["previous":"Error":private] => NULL } 

كما ترون ، لقد اكتشفوا استثناء ParseError ، وهو خليفة الاستثناء Errorالذي ينفذ الواجهة Throwableفي المنزل الذي قام جاك ببنائه. هناك العديد من الاستثناءات الأخرى ، لكنني لن أعذب - من أجل الوضوح ، سأقدم تسلسل هرمي للاستثناءات:

 interface Throwable |- Exception implements Throwable | |- ErrorException extends Exception | |- ... extends Exception | `- ... extends Exception `- Error implements Throwable |- TypeError extends Error |- ParseError extends Error |- ArithmeticError extends Error | `- DivisionByZeroError extends ArithmeticError `- AssertionError extends Error 

والمزيد من التفاصيل:

TypeError - للأخطاء عندما لا يتطابق نوع وسيطات الدالة مع النوع الذي تم تمريره في:

 try { (function(int $one, int $two) { return; })('one', 'two'); } catch (TypeError $e) { echo $e->getMessage(); } 

ArithmeticError - قد يحدث أثناء العمليات الحسابية ، على سبيل المثال ، عندما تتجاوز نتيجة الحساب الحد المخصص لعدد صحيح:

 try { 1 << -1; } catch (ArithmeticError $e) { echo $e->getMessage(); } 

DivisionByZeroError - القسمة على خطأ صفر:

 try { 1 / 0; } catch (ArithmeticError $e) { echo $e->getMessage(); } 

AssertionError - وحش نادر يظهر عندما تكون الحالة المحددة في assert () غير راضية:

 ini_set('zend.assertions', 1); ini_set('assert.exception', 1); try { assert(1 === 0); } catch (AssertionError $e) { echo $e->getMessage(); } 

في إعدادات توجيه الإنتاج الخادم zend.assertionsو assert.exceptionقطع، وهي محقة في ذلك
ستجد قائمة كاملة من الاستثناءات المحددة مسبقًا في الدليل الرسمي ، في نفس التسلسل الهرمي لاستثناءات SPL .

المهمة
اكتب معالج أخطاء عالميًا لـ PHP7 يستوعب جميع الاستثناءات الممكنة.

عند كتابة هذا القسم ، تم استخدام مواد من المقال استثناءات وأخطاء Throwable في PHP 7 .

التوحيد


- هناك أخطاء ، استثناءات ، ولكن هل يمكن أن يصل كل هذا بطريقة ما إلى الكومة؟

نعم ، من السهل ، لدينا set_error_handler()واحد ولا أحد سيمنعنا من وضع استثناء داخل هذا المعالج:

 //     function errorHandler($severity, $message, $file = null, $line = null) { //  ,       @ if (error_reporting() === 0) { return false; } throw new \ErrorException($message, 0, $severity, $file, $line); } //   -  set_error_handler('errorHandler', E_ALL); 

ولكن هذا النهج مع PHP7 لا لزوم له ، والآن يمكنه التعامل مع كل شيء Throwable:

 try { /** ... **/ } catch (\Throwable $e) { //      echo $e->getMessage(); } 

تصحيح الأخطاء


أحيانًا ، لتصحيح التعليمات البرمجية ، تحتاج إلى تتبع ما حدث لمتغير أو كائن في مرحلة معينة ، ولهذه الأغراض هناك دالة debug_backtrace () و debug_print_backtrace () ستعيد محفوظات المكالمات إلى الوظائف / الأساليب بالترتيب العكسي:

 <?php function example() { echo '<pre>'; debug_print_backtrace(); echo '</pre>'; } class ExampleClass { public static function method () { example(); } } ExampleClass::method(); 

نتيجة لتنفيذ الوظيفة debug_print_backtrace()، سيتم عرض قائمة المكالمات التي أوصلتنا إلى هذه النقطة:

 #0 example() called at [/www/education/error/backtrace.php:10] #1 ExampleClass::method() called at [/www/education/error/backtrace.php:14] 

يمكنك التحقق من الشفرة بحثًا عن أخطاء في بناء الجملة باستخدام دالة php_check_syntax () أو الأمر php -l [ ]، لكنني لم أرَ استخدامًا لها.

تأكيد


أود أيضًا أن أتحدث عن وحش غريب مثل تأكيد () في PHP. في الواقع ، يمكن اعتبار هذه القطعة تقليدًا لمنهجية برمجة العقود ، ومن ثم سأخبرك كيف لم استخدمها مطلقًا :)
assert()غيرت الوظيفة سلوكها أثناء الانتقال من الإصدار 5.6 إلى 7.0 ، وتغير كل شيء بشكل أقوى في الإصدار 7.2 ، لذلك اقرأ بعناية changelogs و PHP؛)
الحالة الأولى هي عندما تحتاج إلى كتابة TODO مباشرة في الشفرة ، بحيث لا تنس تنفيذ الوظيفة المحددة:

 //  asserts  php.ini // zend.assertions=1 assert(false, "Remove it!"); 

نتيجة لتنفيذ هذا الرمز ، نحصل على E_WARNING:

 Warning: assert(): Remove it! failed 

يمكن تبديل PHP7 إلى وضع الاستثناء ، وبدلاً من حدوث خطأ ، سيظهر استثناء دائمًا AssertionError:

 //    «» ini_set('assert.exception', 1); assert(false, "Remove it!"); 

نتيجة لذلك ، نتوقع استثناء AssertionError.

إذا لزم الأمر ، يمكنك رمي استثناء تعسفي:

 assert(false, new Exception("Remove it!")); 

أوصي باستخدام العلامات @TODO، تعمل معرفات IDE الحديثة بشكل جيد معهم ، ولن تحتاج إلى بذل المزيد من الجهد والموارد للعمل معهم ، على الرغم من أن الإغراء "بالتسجيل" معهم كبير
حالة الاستخدام الثانية هي إنشاء نوع من أنواع TDD ، لكن تذكر أن هذا مجرد تشابه. على الرغم من أنك إذا حاولت جاهدة ، يمكنك الحصول على نتيجة مضحكة تساعد في اختبار التعليمات البرمجية الخاصة بك:

 // callback-      function backlog($script, $line, $code, $message) { echo $message; } //  callback- assert_options(ASSERT_CALLBACK, 'backlog'); //    assert_options(ASSERT_WARNING, false); //      assert(sqr(4) === 16, 'When I send integer, function should return square of it'); // ,   function sqr($a) { return; //    } 

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

 /** *        * * [ * 'host' => 'localhost', * 'port' => 3306, * 'name' => 'dbname', * 'user' => 'root', * 'pass' => '' * ] * * @param $settings */ function setupDb ($settings) { //   assert(isset($settings['host']), 'Db `host` is required'); assert(isset($settings['port']) && is_int($settings['port']), 'Db `port` is required, should be integer'); assert(isset($settings['name']), 'Db `name` is required, should be integer'); //    // ... } setupDb(['host' => 'localhost']); 


إذا كنت مهتمًا بالعقود ، فبالنسبة لك على وجه التحديد ، لديّ ارتباط بإطار عمل PhpDeal .


لا تستخدم مطلقًا assert()للتحقق من معلمات الإدخال ، لأنها في الواقع assert()تفسر المعلمة الأولى (تتصرف مثل eval()) ، وهذا محفوف بحقن PHP. ونعم ، هذا هو السلوك الصحيح ، لأنه إذا قمت بتعطيل التأكيد ، فسيتم تجاهل كل الحجج التي تم تمريرها ، وإذا قمت بذلك كما هو موضح في المثال أعلاه ، سيتم تنفيذ الرمز ، وسيتم تمرير النتيجة المنطقية للتنفيذ داخل التأكيد المعطل. أوه ، وتغيرت في PHP 7.2 :)


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

في الختام


سأكتب الاستنتاجات من هذه المقالة لك:

  • أخطاء القتال - يجب ألا تكون في التعليمات البرمجية الخاصة بك
  • استخدم الاستثناءات - يجب تنظيم العمل معهم بشكل صحيح وستكون هناك سعادة
  • تأكيد - علمت عنهم ، وكذلك

PS


هذا هو repost من سلسلة من المقالات "PHP للمبتدئين":


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

بفضل مكسيم Slesarenko للمساعدة في كتابة المقال.

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


All Articles