معالجة الاستثناءات في PHP

يستخدم الاستثناء (Exception) لتغيير مسار التنفيذ الطبيعي للسكربت عند حدوث خطأ معين. هذه الحالة تسمى استثناء.

ما هو الاستثناء؟

يقدم PHP 5 طريقة جديدة للمعالجة التي تعتمد على الكائنات.

يستخدم معالجة الاستثناءات لتغيير مسار التنفيذ الطبيعي للسكربت عند حدوث خطأ (استثناء) معين. هذا الحالة تسمى استثناء.

عندما يتم استثارة الاستثناء، عادةً ما يحدث ما يلي:

  • تم حفظ حالة الكود الحالية
  • تم تحويل تنفيذ الكود إلى دالة معالج الاستثناءات المسبقة
  • بناءً على الحالة، قد يعيد المعالج التنفيذ من حالة الكود المحفوظة، يوقف تنفيذ السكربت، أو يستمر في تنفيذ السكربت من مكان آخر في الكود

سنعرض مختلف طرق معالجة الأخطاء:

  • استخدام أساسي للاستثناءات
  • إنشاء معالج استثناءات مخصص
  • عدة استثناءات
  • إعادة إلقاء الاستثناء
  • تعيين معالج الاستثناءات المرتفع

استخدام أساسي للاستثناءات

عندما يتم إلقاء الاستثناء، لن يستمر الكود التالي في التنفيذ، سيحاول PHP البحث عن كتلة "catch" التي تتطابق مع الاستثناء.

إذا لم يتم القبض على الاستثناء، وكان لم يتم استخدام set_exception_handler() لمعالجة الاستثناء بشكل مناسب، فإنه سيحدث خطأ خطير (خطأ حاسم) وسيتم إظهار رسالة خطأ "Uncaught Exception" (استثناء لم يتم القبض عليه).

دعونا نحاول إلقاء استثناء، دون محاولة القبض عليه:

<?php
//إنشاء دالة تحتوي على استثناء
function checkNum($number)
 {
 if($number>1)
  {
  throw new Exception("Value must be 1 or below");
  }
 return true;
 }
//استثارة الاستثناء
checkNum(2);
?>

سيكون الكود أعلاه مشابهاً لهذا الخطأ:

خطأ حاسم: استثناء لم يتم القبض عليه 'Exception' 
مع رسالة 'يجب أن يكون القيمة 1 أو أقل' في C:\webfolder\test.php:6 
تتبع ال堆: #0 C:\webfolder\test.php(12): 
checkNum(28) #1 {main} مرفوع في C:\webfolder\test.php في الخط 6

Try, throw و catch

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

يجب أن يشمل التعامل مع الاستثناءات ما يلي:

  1. حاول - يجب أن تكون الدوال التي تتسبب في استثارة الاستثناءات موضوعة داخل كتلة "حاول". إذا لم يتم استثارة الاستثناء، فإن الكود سيستمر في التنفيذ كما هو. ولكن إذا تم استثارة الاستثناء، سيتم إلقاء استثناء.
  2. Throw - يحدد هذا كيفية تحفيز الاستثناء. يجب أن يكون كل "throw" له "catch" على الأقل
  3. Catch - "catch" block will catch the exception and create an object containing exception information

لنقوم بتحفيز استثناء:

<?php
//Create a function that can throw an exception
function checkNum($number)
 {
 if($number>1)
  {
  throw new Exception("Value must be 1 or below");
  }
 return true;
 }
//Trigger exception in the "try" block
try
 {
 checkNum(2);
 //If the exception is thrown, this text will not be shown
 echo 'If you see this, the number is 1 or below';
 }
//Catch exception
catch(Exception $e)
 {
 echo 'Message: ' . $e->getMessage();
 }
?>

سيتم الحصول على خطأ مشابه لما يلي:

Message: Value must be 1 or below

شرح المثال:

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

  1. إنشاء وظيفة checkNum(). تتحقق من whether the number is greater than 1. إذا كان كذلك، يُرمى استثناء.
  2. استدعاء وظيفة checkNum() في block "try".
  3. يتم رمي الاستثناء في وظيفة checkNum()
  4. "catch" block يستقبل هذا الاستثناء ويخلق كائنًا يحتوي على معلومات الاستثناء ($e).
  5. من خلال استدعاء $e->getMessage() من هذا استثناء، يتم إرجاع رسالة الخطأ من هذا الاستثناء

على الرغم من ذلك، لاتباع مبدأ "كل throw يجب أن يكون له catch"، يمكنك تعيين معالج استثناء عالي المستوى لمعالجة الأخطاء المفقودة.

إنشاء فئة "Exception" مخصصة

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

تعتمد هذه الفئة المخصصة على جميع خصائص فئة "exception" في PHP، يمكنك إضافة وظائف مخصصة لها.

نبدأ بإنشاء فئة "exception":

<?php
class customException extends Exception
 {
 public function errorMessage()
  {
  //رسالة خطأ
  $errorMsg = 'Error on line '.$this->getLine().' in '.$this->getFile()
  : <b>'.$this->getMessage().'</b> ليست عنوان بريد إلكتروني صالحاً
  return $errorMsg;
  }
 }
$email = "+[email protected]...com+;
try
 {
 //تحقق إذا 
 if(filter_var($email, FILTER_VALIDATE_EMAIL) === FALSE)
  {
  //throw الاستثناء إذا كان البريد الإلكتروني غير صالح
  throw new customException($email);
  }
 }
catch (customException $e)
 {
 //عرض رسالة مخصصة
 echo $e->errorMessage();
 }
?>

هذه الفئة الجديدة هي نسخة من فئة الاستثناء القديمة،مضافاً إليها دالة errorMessage().بما أنها نسخة من الفئة القديمة،فإنها تورث الخصائص والأساليب من الفئة القديمة،ويمكننا استخدام أساليب فئة exception،مثل getLine() وgetFile() وgetMessage().

شرح المثال:

يُلقى الكود المذكور أعلاه استثناء،ويتم إلتقاطه من خلال استثناء custom.

  1. تم إنشاء فئة customException كامتداد لفئة exception القديمة. بهذا يورثها من جميع الخصائص والأساليب القديمة.
  2. إنشاء دالة errorMessage().إذا كان عنوان البريد الإلكتروني غير صالح،فسيقوم بتحويل هذه الدالة إلى رسالة خطأ
  3. تعيين المتغير $email إلى سلسلة نصية غير صالحة لعنوان البريد الإلكتروني
  4. إجراء قالب "try"،بسبب أن عنوان البريد الإلكتروني غير صالح،سيتم إطلاق استثناء
  5. قالب "catch" يلتقط الاستثناء ويظهر رسالة الخطأ

عدة استثناءات

يمكن استخدام استثناءات متعددة لسطر البرنامج لتحديد عدة حالات.

يمكن استخدام عدة قوالب if..else،أو قالب switch،أو تضمين عدة استثناءات.هذه الاستثناءات تستخدم أنواع مختلفة من استثناءات exception وتقوم بإرجاع رسائل خطأ مختلفة:

<?php
class customException extends Exception
{
public function errorMessage()
{
//رسالة خطأ
$errorMsg = 'Error on line '.$this->getLine().' in '.$this->getFile()
: <b>'.$this->getMessage().'</b> ليست عنوان بريد إلكتروني صالحاً
return $errorMsg;
}
}
$email = "someone@example.com";
try
 {
 //تحقق إذا 
 if(filter_var($email, FILTER_VALIDATE_EMAIL) === FALSE)
  {
  //throw الاستثناء إذا كان البريد الإلكتروني غير صالح
  throw new customException($email);
  }
 //تحقق من وجود "example" في عنوان البريد الإلكتروني
 if(strpos($email, "example") !== FALSE)
  {
  throw new Exception("$email is an example e-mail");
  }
 }
catch (customException $e)
 {
 echo $e->errorMessage();
 }
catch(Exception $e)
 {
 echo $e->getMessage();
 }
?>

شرح المثال:

يختبر الكود المذكور أعلاه شروطين،وإذا لم يتحقق أي منها،سيتم إطلاق استثناء:

  1. تم إنشاء فئة customException كامتداد لفئة exception القديمة. بهذا يورثها من جميع الخصائص والأساليب القديمة.
  2. تم إنشاء دالة errorMessage(). إذا كان عنوان البريد الإلكتروني غير صالح، فإن هذه الدالة تعود رسالة خطأ.
  3. إجراء قالب "try"،في الشروط الأولى،لن يتم إطلاق الاستثناء.
  4. بسبب أن البريد الإلكتروني يحتوي على النص "example"،سيتم إطلاق الاستثناء عند تحقق الثانية.
  5. قالب "catch" سيقوم بإلتقاط الاستثناء وعرض رسالة خطأ مناسبة

إذا لم يتم إلتقاط customException،وتم إلتقاط base exception فقط،فسيتم التعامل مع الاستثناء هناك.

إعادة إلقاء الاستثناء

في بعض الأحيان،عندما يتم إلقاء استثناء،قد ترغب في التعامل معه بطريقة مختلفة عن المعايير القياسية.يمكنك إعادة إلقاء الاستثناء في قالب "catch".

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

<?php
class customException extends Exception
 {
 public function errorMessage()
  {
  //رسالة خطأ
  $errorMsg = $this->getMessage().' is not a valid E-Mail address.';
  return $errorMsg;
  }
 }
$email = "someone@example.com";
try
 {
 try
  {
  //تحقق من وجود "example" في عنوان البريد الإلكتروني
  if(strpos($email, "example") !== FALSE)
   {
   //throw الاستثناء إذا كان البريد الإلكتروني غير صالح
   throw new Exception($email);
   }
  }
 catch(Exception $e)
  {
  //إعادة throw الاستثناء
  throw new customException($email);
  }
 }
catch (customException $e)
 {
 //عرض رسالة مخصصة
 echo $e->errorMessage();
 }
?>

شرح المثال:

يكتشف الكود أعلاه ما إذا كان يحتوي عنوان البريد الإلكتروني على النص "example". إذا كان كذلك، يتم throw الاستثناء مرة أخرى:

  1. تم إنشاء فئة customException كامتداد لفئة exception القديمة. بهذا يورثها من جميع الخصائص والأساليب القديمة.
  2. تم إنشاء دالة errorMessage(). إذا كان عنوان البريد الإلكتروني غير صالح، فإن هذه الدالة تعود رسالة خطأ.
  3. تم تعيين متغير $email إلى عنوان بريد إلكتروني صالح، لكنه يحتوي على النص "example".
  4. يحتوي كود "try" على كود "try" آخر، مما يتيح throw الاستثناء مرة أخرى.
  5. بسبب أن بريد الإلكتروني يحتوي على النص "example"، تم trigger الاستثناء.
  6. "catch" تم captures هذا الاستثناء وتم إعادة throw "customException".
  7. تم captures "customException" وعرض رسالة خطأ.

إذا لم يتم إلتقاط الاستثناء في كود "try" الحالي، فإنه سيبحث في مستوى أعلى عن كود "catch".

设置顶层异常处理器 (Top Level Exception Handler)

set_exception_handler() 函数可设置处理所有未捕获异常的用户定义函数。

<?php
function myException($exception)
{
echo "<b>Exception:</b> " , $exception->getMessage();
}
set_exception_handler('myException');
throw new Exception('Uncaught Exception occurred');
?>

以上代码的输出应该类似这样:

Exception: Uncaught Exception occurred

在上面的代码中,不存在 "catch" 代码块,而是触发顶层的异常处理程序。应该使用此函数来捕获所有未被捕获的异常。

异常的规则

  • 需要进行异常处理的代码应该放入 try 代码块内,以便捕获潜在的异常。
  • 每个 try 或 throw 代码块必须至少拥有一个对应的 catch 代码块。
  • 使用多个 catch 代码块可以捕获不同种类的异常。
  • 可以在 try 代码块内的 catch 代码块中再次抛出(re-thrown)异常。

简而言之:如果抛出了异常,就必须捕获它。