مدیریت استثناها در PHP

استثنا (Exception) برای تغییر مسیر عادی اجرای اسکریپت در شرایط خاص خطا استفاده می‌شود.

استثنا چیست؟

PHP 5 روش جدیدی برای مدیریت خطا به صورت شیءگرا ارائه داده است.

مدیریت استثنا برای تغییر مسیر عادی اجرای اسکریپت در شرایط خاص خطا (استثنا) استفاده می‌شود. این وضعیت به عنوان استثنا شناخته می‌شود.

وقتی استثنا فعال می‌شود، معمولاً اتفاقاتی مانند زیر رخ می‌دهد:

  • وضعیت کد فعلی ذخیره می‌شود
  • اجرای کد به مدیریتکننده استثنا پیش‌تعیین شده تغییر می‌کند
  • در برخی موارد، مدیریتکننده ممکن است از وضعیت کد ذخیره شده مجدداً شروع به اجرای کد کند، اجرای اسکریپت را متوقف کند یا از مکان دیگری در کد اجرای اسکریپت را ادامه دهد

ما روش‌های مختلف مدیریت خطا را نشان خواهیم داد:

  • استفاده پایه‌ای از استثنا
  • ایجاد مدیریتکننده استثنا سفارشی
  • استثناها
  • پرتاب مجدد استثنا
  • تنظیم مدیریتکننده استثنا سطح بالا

استفاده پایه‌ای از استثنا

وقتی استثنا پرتاب می‌شود، کد بعدی اجرا نمی‌شود، PHP سعی می‌کند بلوک کد "catch" مناسب را پیدا کند.

اگر استثنا گرفته نشد و از set_exception_handler() برای مدیریت مناسب استفاده نشده باشد، یک خطای جدی (خطای حیاتی) رخ خواهد داد و پیام خطای "Uncaught Exception" (استثنا غیرگرفته شده) نمایش داده خواهد شد.

بیایید تلاش کنیم استثنا را پرتاب کنیم و آن را نگیریم:

<?php
//تابعی ایجاد کنید که استثنا دارد
function checkNum($number)
 {
 if($number>1)
  {
  throw new Exception("ارزش باید 1 یا کمتر باشد");
  }
 return true;
 }
//استثنا فعال شود
checkNum(2);
?>

کد بالا خطایی مانند زیر را به دست خواهد آورد:

خطای حیاتی: استثنا غیرمنتظره 'Exception' 
با پیام 'مقدار باید 1 یا کمتر باشد' در C:\webfolder\test.php:6 
آدرس لغزش: #0 C:\webfolder\test.php(12): 
checkNum(28) #1 {main} در خط 6 از C:\webfolder\test.php ارسال شد

Try, throw و catch

برای جلوگیری از خطاهایی که در مثال بالا مشاهده کردیم، باید کد مناسب را برای مدیریت استثنا ایجاد کنیم.

برنامه‌ریزی مناسب باید شامل موارد زیر باشد:

  1. سعی کنید - استفاده از تابع‌های استثنایی باید در بلوک کد "سعی" قرار گیرد. اگر استثنا فعال نشود، کد به صورت عادی ادامه خواهد داد. اما اگر استثنا فعال شد، یک استثنا ارسال خواهد شد.
  2. Throw - در اینجا روشی که استثنا باید تحریک شود، مشخص شده است. هر "throw" باید حداقل با یک "catch" مطابقت داشته باشد
  3. Catch - بخش "catch" کد، استثنا را می‌گیرد و یک شیء حاوی اطلاعات استثنا ($e) ایجاد می‌کند

بیایید استثنایی تحریک کنیم:

<?php
//ایجاد توابعی که می‌توانند استثنا پرتاب کنند
function checkNum($number)
 {
 if($number>1)
  {
  throw new Exception("ارزش باید 1 یا کمتر باشد");
  }
 return true;
 }
//استثنا در بخش "try" تحریک می‌شود
try
 {
 checkNum(2);
 //اگر استثنا پرتاب شود، این متن نمایش داده نمی‌شود
 echo 'اگر این را ببینید، عدد 1 یا کمتر است';
 }
//گرفتن استثنا
catch(Exception $e)
 {
 echo 'پیام: ' .$e->getMessage();
 }
?>

کد بالا خطایی مانند این را دریافت می‌کند:

پیام: ارزش باید 1 یا کمتر باشد

例子解释:

کد بالا یک استثنا پرتاب می‌کند و آن را می‌گیرد:

  1. توابع checkNum() ایجاد می‌شود. این توابع بررسی می‌کند که آیا عدد بزرگتر از 1 است یا خیر. اگر باشد، یک استثنا پرتاب می‌کند.
  2. توابع checkNum() در بخش "try" فراخوانی می‌شود.
  3. استثنا در توابع checkNum() پرتاب می‌شود
  4. بخش "catch" کد، این استثنا را دریافت می‌کند و یک شیء حاوی اطلاعات استثنا ($e) ایجاد می‌کند.
  5. از طریق فراخوانی $e->getMessage() از این شیء exception، پیام خطای استثنا را از آن دریافت می‌کنیم

اما برای پیروی از اصل «هر throw باید با یک catch مطابقت داشته باشد»، می‌توان یک پردازش‌کننده‌ی استثنا سطح بالایی برای مدیریت خطاهای گم شده تنظیم کرد.

ایجاد یک کلاس Exception شخصی

ایجاد یک برنامه‌کننده‌ی استثنایی شخصی بسیار آسان است. ما به سادگی یک کلاس اختصاصی ایجاد کرده‌ایم که می‌توان از آن توابع را در زمان بروز استثنا در PHP فراخوانی کرد. این کلاس باید یک گسترش از کلاس exception باشد.

این کلاس exception تعریف شده، تمام ویژگی‌های کلاس exception PHP را به ارث برده است و شما می‌توانید توابع شخصی به آن اضافه کنید.

ما شروع به ایجاد کلاس exception می‌کنیم:

<?php
class customException extends Exception
 {
 public function errorMessage()
  {
  //error message
  $errorMsg = 'Error on line '.$this->getLine().' in '.$this->getFile()
  ': <b>'.$this->getMessage().'</b> آدرس ایمیل معتبر نیست';
  return $errorMsg;
  }
 }
$email = ";example@example...com";
try
 {
 //check if 
 if(filter_var($email, FILTER_VALIDATE_EMAIL) === FALSE)
  {
  //throw exception if email is not valid
  throw new customException($email);
  }
 }
catch (customException $e)
 {
 //display custom message
 echo $e->errorMessage();
 }
?>

این کلاس جدید یک کپی از کلاس استثنا قدیمی است و functشن errorMessage() را اضافه کرده است. به دلیل اینکه این کلاس یک کپی از کلاس قدیمی است، از آن به ارث می‌برد ویژگی‌ها و روش‌ها، بنابراین می‌توانیم از روش‌های کلاس استثنا استفاده کنیم، مانند getLine()، getFile() و getMessage().

例子解释:

کد بالا یک استثنا پرتاب می‌کند و آن را با استفاده از یک کلاس استثنا سفارشی می‌گیرد:

  1. customException() 类是作为旧的 exception 类的一个扩展来创建的。这样它就继承了旧类的所有属性和方法。
  2. функشن errorMessage() را ایجاد کنید. اگر آدرس ایمیل نامعتبر باشد، این функشن پیام خطایی بازمی‌گرداند
  3. $email را به یک رشته آدرس ایمیل نامعتبر تنظیم کنید
  4. بلوک "try" را اجرا کنید، به دلیل اینکه آدرس ایمیل نامعتبر است، استثنا پرتاب می‌شود
  5. بلوک "catch" استثنا را می‌گیرد و پیام خطا را نمایش می‌دهد

استثناها

می‌توان برای یک اسکریپت چندین استثنا استفاده کرد تا شرایط مختلف را بررسی کرد.

می‌توان از چندین بلوک if..else یا یک بلوک switch یا چندین استثنا درون‌هم استفاده کرد. این استثناها می‌توانند از کلاس‌های استثنا مختلف استفاده کنند و پیام‌های خطای مختلفی بازگردانند:

<?php
class customException extends Exception
{
public function errorMessage()
{
//error message
$errorMsg = 'Error on line '.$this->getLine().' in '.$this->getFile()
': <b>'.$this->getMessage().'</b> آدرس ایمیل معتبر نیست';
return $errorMsg;
}
}
$email = "someone@example.com";
try
 {
 //check if 
 if(filter_var($email, FILTER_VALIDATE_EMAIL) === FALSE)
  {
  //throw exception if email is not valid
  throw new customException($email);
  }
 //check for "example" in mail address
 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() 函数。如果 e-mail 地址不合法,则该函数返回一个错误消息。
  3. بلوک "try" را اجرا کنید، در شرط اول، استثنا پرتاب نمی‌شود.
  4. به دلیل اینکه ایمیل شامل رشته "example" است، شرط دوم استثنا را فعال می‌کند.
  5. بلوک "catch" استثنا را می‌گیرد و پیام خطای مناسب را نمایش می‌دهد

اگر customException نگرفته باشد و فقط base exception گرفته شده باشد، در آنجا استثنا را مدیریت کنید.

پرتاب مجدد استثنا

گاهی اوقات، وقتی یک استثنا پرتاب می‌شود، ممکن است بخواهید به روشی متفاوت از استاندارد با آن برخورد کنید. می‌توانید استثنا را در یک بلوک "catch" دوباره پرتاب کنید.

脚本应该对用户隐藏系统错误。对程序员来说,系统错误也许很重要,但是用户对它们并不感兴趣。为了让用户更容易使用,您可以再次抛出带有对用户比较友好的消息的异常:

<?php
class customException extends Exception
 {
 public function errorMessage()
  {
  //error message
  $errorMsg = $this->getMessage().' is not a valid E-Mail address.';
  return $errorMsg;
  }
 }
$email = "someone@example.com";
try
 {
 try
  {
  //check for "example" in mail address
  if(strpos($email, "example") !== FALSE)
   {
   //throw exception if email is not valid
   throw new Exception($email);
   }
  }
 catch(Exception $e)
  {
  //re-throw exception
  throw new customException($email);
  }
 }
catch (customException $e)
 {
 //display custom message
 echo $e->errorMessage();
 }
?>

例子解释:

上面的代码检测在邮件地址中是否含有字符串 "example"。如果有,则再次抛出异常:

  1. customException() 类是作为旧的 exception 类的一个扩展来创建的。这样它就继承了旧类的所有属性和方法。
  2. 创建 errorMessage() 函数。如果 e-mail 地址不合法,则该函数返回一个错误消息。
  3. 把 $email 变量设置为一个有效的邮件地址,但含有字符串 "example"。
  4. "try" 代码块包含另一个 "try" 代码块,这样就可以再次抛出异常。
  5. 由于 e-mail 包含字符串 "example",因此触发异常。
  6. "catch" 捕获到该异常,并重新抛出 "customException"。
  7. 捕获到 "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)异常。

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