7. استثناها و خطاها
هنگامی که یک متد کلاس با یک خطای غیرقابل بازیابی مواجه میشود (فایل وجود ندارد، پایگاه داده متصل نیست، اتصال شبکه قطع است)، خطا را در کنسول (فایل، پایگاه داده) نمایش نمیدهد، بلکه یک استثنا پرتاب میکند. تمام استثناها از کلاس [\Exception] ارث میبرند. علاوه بر استثناها، عملیات داخلی PHP نیز خطاهایی تولید میکند که کلاس پایه آنها [\Error] است. هر دو کلاس رابطهای PHP و [\Throwable] را پیادهسازی میکنند.
7.1. درخت اسکریپت

7.2. رابط [\Throwable]
رابط [\Throwable] به شرح زیر است:

نقش متدها در این رابط به شرح زیر است:

7.3. استثناهای از پیش تعریفشده در PHP 7
PHP 7 چندین کلاس استثنا را تعریف میکند:

- در [1]، استثناءهای از پیش تعریفشده در PHP؛
- در [2]، استثناها از کتابخانه SPL (کتابخانه استاندارد PHP) از PHP 7. کتابخانه SPL مجموعهای از کلاسها و رابطها است که برای حل مشکلات رایج توسعهدهندگان طراحی شده است.
7.4. خطاهای از پیش تعریفشده در PHP 7
کتابخانه PHP 7 چندین کلاس خطا را تعریف میکند:

کلاس [\Error] کلاس والد تمام خطاهای از پیش تعریفشده در PHP است. کلاس [ErrorException] اجازه میدهد یک نمونه از کلاس [\Error] در درون یک نمونه از کلاس [\Exception] قرار گیرد. این امر با رسیدگی صرف به استثناها، امکان مدیریت خطاهای استاندارد را فراهم میکند.
7.5. مثال ۱
مثال اول، [exceptions-01.php]، هم خطاهای PHP و هم یک استثنا را نشان میدهد:
<?php
// نمایش تمام خطاها
ini_set("error_reporting", E_ALL);
ini_set("display_errors", "on");
//کد --------
$var=[];
//کلید نامشخص
print $var["abcd"];
// تقسیم بر صفر
$var=7/0;
var_dump($var);
// آرایه با محدودههای ثابت
$array = new \SplFixedArray(5);
$array[1] = 2;
$array[4] = "foo";
// شاخص خارج از محدوده
$array[5]=8;
نظرات
- خط ۴: به PHP دستور داده شده است که همه خطاها را گزارش دهد. پارامتر دوم سطح خطای مورد نیاز است:


- خط ۵: از ما خواسته شده است که خطاها روی کنسول نمایش داده شوند؛
- خط ۹: تلاشی برای دسترسی به یک عنصر وجود ندارد در آرایه [$var] انجام میشود؛
- خط ۱۱: تقسیم بر صفر انجام میشود؛
- خط ۱۴: یک نمونه از کلاس [SplFixedArray] ایجاد میشود. این کلاس امکان ایجاد یک آرایه با مرزهای ثابت و شاخصهای عددی را فراهم میکند؛
- خط ۱۸: تلاشی برای دسترسی به یک عنصر وجود ندارد در آرایه صورت میگیرد؛
نتایج
توضیحات
- خط ۱ نتایج: دسترسی به یک کلید وجود نداشته در یک آرایه، باعث خطای PHP با سطح [E_NOTICE] میشود. این امر اجرای اسکریپت را متوقف نمیکند؛
- خط ۳ نتایج: تقسیم یک عدد بر صفر باعث خطای PHP با سطح [E_WARNING] میشود. این امر اجرای اسکریپت را متوقف نمیکند؛
- خطوط ۶–۹ نتایج: دسترسی به یک اندیس وجودندارنده در یک آرایه [SplFixedArray] باعث ایجاد یک استثنا از نوع [RuntimeException] شده و اجرای اسکریپت را متوقف میکند؛
7.6. مدیریت استثناها
اسکریپت [exceptions-02.php] نشان میدهد چگونه با خطاها برخورد شود:
<?php
// تمام خطاها نمایش داده میشوند
ini_set("error_reporting", E_ALL);
ini_set("display_errors", "on");
// کد را در یک بلوک try/catch قرار دهید
try {
$var = [];
// کلید نامشخص
print $var["abcd"];
//تقسیم بر صفر
$var = 7 / 0;
var_dump($var);
// آرایه با محدودههای ثابت
$array = new \SplFixedArray(5);
$array[1] = 2;
$array[4] = "foo";
// شاخص خارج از محدوده
$array[5] = 8;
// بررسی
print "ce message ne sera pas affiché\n";
} catch (\Throwable $ex) {
// \Throwable رابطی است که توسط اکثر خطاها و استثناها پیادهسازی میشود
// استثناء نمایش داده میشود
print "erreur, message : " . $ex->getMessage() . ", type : " . get_class($ex) . "\n";
}
توضیحات
- این همان اسکریپت ارائهشده در پاراگراف قبلی است. با این حال، کدی که در خطوط ۸ تا ۱۹ قرار دارد و احتمالاً باعث خطا میشود، اکنون در داخل یک بلوک try/catch قرار گرفته است: اگر کد در خطوط ۸ تا ۲۱ یک استثنا یا خطا ایجاد (throw) کند، این مورد توسط بخش catch در خطوط ۲۲ تا ۲۶ مدیریت خواهد شد؛
- خط ۲۲: پارامتر بند [catch] نوع استثناء یا خطایی است که باید مدیریت شود. با مشخص کردن نوع به عنوان [\Throwable] که یک رابط است، نشان میدهیم که میخواهیم هر نمونه کلاسی را که رابط [\Throwable] را پیادهسازی میکند، مدیریت کنیم. از آنجایی که تمام کلاسهای خطا و استثنا این رابط را پیادهسازی میکنند، بند [catch] در اینجا هر خطا یا استثنایی را که در یک کلاس جای گرفته است، مدیریت میکند؛
- خط ۱۹: دستوری که خطا را ایجاد کرده و استثناء را بالا میآورد. به محض وقوع استثناء، کنترل به بند [catch] منتقل میشود. بنابراین کد پس از خط ۱۹ اجرا نخواهد شد؛
نتایج
توضیحات درباره نتایج
- خطوط ۱ و ۳: خطاها در سطح [E_NOTICE] و [E_WARNING] یافت میشوند. این خطاها استثنا نیستند و بنابراین توسط بند [catch] رسیدگی نمیشوند؛
- خط ۵: پیام خطا در بند [catch] مشخص شده است. این بدان معناست که یا یک استثنا مشتقشده از [\Exception] یا یک خطا مشتقشده از [\Error] رخ داده است. در اینجا میبینیم که این مربوط به کلاس [\RuntimeException] است؛
7.7. پارامترهای بند [catch]
بیایید اسکریپت زیر [exceptions-03.php] را بررسی کنیم:
<?php
// تمام خطاها نمایش داده میشوند
ini_set("error_reporting", E_ALL);
ini_set("display_errors", "on");
//یک آرایه با محدودههای ثابت
$array = new \SplFixedArray(5);
try {
// شاخص خارج از محدوده
$array[5] = 8;
} catch (\Throwable $ex) {
// نمایش پیام خطا
print "Erreur 1 : " . $ex->getMessage() . "\n";
}
try {
// شاخص خارج از محدوده
$array[5] = 8;
} catch (\Exception $ex) {
// نمایش پیام خطا
print "Erreur 2 : " . $ex->getMessage() . "\n";
}
try {
// شاخص خارج از محدوده
$array[5] = 8;
} catch (\RuntimeException $ex) {
// پیام خطا نمایش داده شد
print "Erreur 3 : " . $ex->getMessage() . "\n";
}
try {
//تقسیم بر صفر
intdiv(5, 0);
} catch (\Throwable $ex) {
// پیام خطا نمایش داده شد
print "Erreur 4 : " . $ex->getMessage() . "\n";
}
try {
//تقسیم بر صفر
intdiv(5, 0);
} catch (\DivisionByzeroError $ex) {
// نمایش پیام خطا
print "Erreur 5 : " . $ex->getMessage() . "\n";
}
try {
//تقسیم بر صفر
intdiv(5, 0);
} catch (\Error $ex) {
// نمایش پیام خطا
print "Erreur 6 : " . $ex->getMessage() . "\n";
}
try {
//تقسیم بر صفر
intdiv(5, 0);
} catch (\Exception $ex) {
//نمایش پیام خطا
print "Erreur 6 : " . $ex->getMessage() . "\n";
}
توضیحات
- خطوط ۸–۳۱: سه روش مختلف برای رسیدگی به استثنایی که با استفاده از یک شاخص نادرست در کلاس [\SplFixedArray] ایجاد میشود. ما دیدهایم که این خطا یک استثنای از نوع [RuntimeException] تولید میکند؛
- خط ۱۲: خطایی با نوع [\Throwable] را مدیریت میکند. این کار معتبر است زیرا نوع [RuntimeException] از نوع [\Exception] مشتق شده است، که رابط [\Throwable] را پیادهسازی میکند؛
- خط ۲۰: یک خطای از نوع [\Exception] را مدیریت میکند. این معتبر است زیرا نوع [RuntimeException] از نوع [\Exception] ارث میبرد؛
- خط ۲۸: خطایی از نوع [\RuntimeException] را مدیریت میکند. این روش ترجیحی است زیرا دقیقاً همان نوع استثنای تولیدشده است؛
- خطوط ۳۲–۶۲: چهار روش مختلف برای مدیریت استثنایی که توسط تابع [intdiv] هنگام ارسال تقسیمشوندهای برابر با ۰ ایجاد میشود. تابع [ intdiv ( int $dividend , int $divisor ) : int] تقسیم درستی $dividend / $divisor را انجام میدهد. هنگامی که مخرج صفر باشد، استثنای [\DivisionByzeroError] پرتاب میشود؛
- خط ۳۵: هر خطایی در پیادهسازی رابط [\Throwable] رهگیری میشود. این صحیح است؛
- خط ۴۳: نوع دقیق خطا دریافت میشود: این روش ترجیحی است؛
- خط ۵۱: نوع [\Error] گرفته میشود. این صحیح است زیرا کلاس [DivisionByzeroError] از کلاس [Error] ارث میبرد؛
- خط ۵۹: نوع [\Exception] رهگیری میشود. این نادرست است زیرا کلاس [DivisionByzeroError] هیچ ارتباطی با کلاس [\Exception] ندارد؛
نتایج
7.8. بند [finally]
بلوک try/catch میتواند یک عنصر سوم داشته باشد و به یک بلوک try/catch/finally تبدیل شود. کد در بلوک [finally] در دو مورد زیر اجرا میشود:
- کلاز [try] استثنا پرتاب نمیکند. سپس به طور کامل اجرا میشود، و پس از آن اجرای برنامه به کلاز [finally] منتقل میشود که آن نیز به طور کامل اجرا میگردد؛
- کلاز [try] یک استثنا پرتاب میکند. سپس تا دستورالعملی که استثنا را پرتاب میکند، اجرا میشود. سپس اجرای کد به بند [catch] منتقل میشود که به طور کامل اجرا میگردد. سپس اجرای کد به بند [finally] منتقل میشود که آن نیز به طور کامل اجرا میگردد؛
در نهایت، کد در بند [finally] همچنان اجرا میشود. این سناریو در مورد زیر مفید است:
- در [try]، کد منابع (فایلها، پایگاههای داده، اتصالات شبکه، صفها) را به دست آورده است. به طور کلی، این منابع نیازمند حافظه زیادی هستند. بنابراین، باید در اسرع وقت آزاد شوند (که معمولاً به آن «بسته شدن» گفته میشود)؛
- اگر منابع در [try] به دست آمده باشند، در [finally] آزاد خواهند شد. این امر تضمین میکند که در تمام موارد (چه خطایی رخ دهد و چه ندهد)، منابع به دست آمده به سیستم بازگردانده شوند؛
اسکریپت زیر، [exemples/exceptions/exceptions-04.php]، نشان میدهد که عبارت [finally] در موقعیتهای مختلف چگونه عمل میکند:
<?php
// یا یک نمونه استثنا ایجاد میکند
$e = new \Exception("Erreur…");
var_dump($e);
// آزمون اول
try {
print "Premier test\n";
throw $e;
} catch (\Exception $ex1) {
print $ex1->getMessage() . "\n";
} finally {
print "Terminé\n";
}
// آزمون دوم
try {
print "Second test\n";
} catch (\Exception $ex1) {
print $ex1->getMessage() . "\n";
} finally {
print "Terminé\n";
}
//سومین آزمون
try {
print "Troisième test\n";
return;
} catch (\Exception $ex1) {
print $ex1->getMessage() . "\n";
} finally {
print "Terminé\n";
}
توضیحات کد
- خط ۴: $e یک نمونه از کلاس از پیش تعریفشده [\Exception] است. ما آن را در مکانهای مختلف نمونه سازی خواهیم کرد؛
- خطوط ۸–۱۵: استثنای $e در داخل [try] (خط ۱۰) پرتاب میشود؛
- خط ۱۱: استثنای [\Exception] گرفته میشود و پیام خطای آن به کنسول نوشته میشود؛
- خطوط ۱۳–۱۵: عبارت [finally] پیامی را مینویسد. بر اساس آنچه قبلاً گفته شد، این پیام باید همیشه نوشته شود، صرفنظر از اینکه آیا خطایی در [try] وجود دارد یا خیر؛
- خطوط ۱۸–۲۴: خطایی در [try] وجود ندارد. در اینجا نیز، کنترل باید به [finally] منتقل شود؛
- خطوط ۲۷–۳۴: یک دستور [return] در بلوک «try» وجود دارد و هیچ خطایی نیست. بنابراین ممکن است این سؤال پیش بیاید که آیا کنترل به شرط [finally] منتقل خواهد شد یا خیر. اجرای برنامه نشان میدهد که چنین است؛
نتایج
بیایید مورد دیگری را بررسی کنیم، [exceptions-05.php]:
<?php
//چهارمین آزمون
try {
print "Quatrième test\n";
exit;
} finally {
print "Terminé\n";
}
توضیحات
- خط ۶: دستور [exit] بلافاصله اجرای اسکریپت را متوقف میکند: بند [finally] اجرا نمیشود؛
- خطوط ۴–۹: مثالی از یک بلوک try / catch / finally بدون شرط [catch]. این امکانپذیر است؛
نتایج
7.9. ایجاد کلاسهای استثنای خود
در یک پروژه نسبتاً بزرگ، تفکیک خطاهای مختلف با قرار دادن آنها در کلاسهای استثنای متفاوت، مفید است. در اسکریپت قبلی دیدیم که هر استثنایی را میتوان با یک عبارت [catch (\Throwable] گرفت. این کار زمانی توصیه میشود که هیچ ایدهای در مورد خطای دریافتشده ندارید و نحوهٔ رسیدگی به همهٔ خطاها یکسان است. گاهی اوقات چنین است، اما اغلب لازم است نحوهٔ رسیدگی را متناسب با نوع دقیق خطا تطبیق دهید. بنابراین باید بین خطاهای مختلف تمایز قائل شوید.
بیایید اسکریپت [exceptions-06.php] زیر را بررسی کنیم:
<?php
// ما خانوادهٔ استثناهای خود را تعریف میکنیم
class Exception1 extends \RuntimeException {
}
class Exception2 extends \RuntimeException {
}
// یا از استثناهای خودمان استفاده کنیم
$e1 = new Exception1("Erreur1…");
var_dump($e1);
$e2 = new Exception2("Erreur2…");
var_dump($e2);
// آزمون اول
print ("premier test\n");
try {
//ما یک Exception1 پرتاب میکنیم
throw $e1;
} catch (Exception1 $ex1) {
print "Exception 1" . "\n";
print $ex1->getMessage() . "\n";
} catch (Exception2 $ex2) {
print "Exception 2" . "\n";
print $ex2->getMessage() . "\n";
}
// آزمون دوم
print ("second test\n");
try {
// ما یک Exception2 پرتاب میکنیم
throw $e2;
} catch (Exception1 $ex1) {
print "Exception 1" . "\n";
print $ex1->getMessage() . "\n";
} catch (Exception2 $ex2) {
print "Exception 2" . "\n";
print $ex2->getMessage() . "\n";
}
// آزمون سوم
print ("troisième test\n");
try {
//یک Exception1 پرتاب میشود
throw $e1;
} catch (Exception1 | Exception2 $ex) {
print "Exception 1 ou 2" . "\n";
print $ex->getMessage() . "\n";
}
// آزمون چهارم
print ("quatrième test\n");
try {
//یک Exception2 پرتاب میشود
throw $e2;
} catch (Exception1 | Exception2 $ex) {
print "Exception 1 ou 2" . "\n";
print $ex->getMessage() . "\n";
}
توضیحات
- خطوط ۴–۱۰: دو کلاس، [Exception1] و [Exception2]، تعریف شدهاند که هر دو از کلاس از پیش تعریفشده [\RuntimeException] ارثبری میکنند. بدنه این کلاسها خالی است. به عبارت دیگر، آنها صرفاً برای نوع خود استفاده میشوند: به این دلیل که نوع متفاوتی دارند، ما قادر خواهیم بود بین این دو استثنا در عبارتهای [catch] تمایز قائل شویم؛
- خطوط ۱۳–۱۶: دو متغیر، $e1 و $e2، تعریف شدهاند که به ترتیب از نوع [Exception1] و [Exception2] هستند؛
- خطوط ۲۰–۲۹: ساختار try/catch/catch وجود دارد. این امکان را میدهد که انواع مختلف استثنا با استفاده از عبارات مختلف [catch];
- خط ۲۳: استثناءهای نوع [Exception1] را میگیرد؛
- خط ۲۶: استثناهای نوع [Exception2] را میگیرد؛
- خط ۴۹: استثناءهای نوع [Exception1] یا (|) [Exception2] را میگیرد؛
نتایج
توضیحات درباره نتایج
- خطوط ۱–۱۷: «محتوا»ی یک استثنا:
- خطوط ۲–۳: پیام خطا؛
- خطوط ۶–۷: کد خطا؛
- خطوط ۸–۹: نام فایلی که در آن استثناء رخ داده است؛
- خطوط ۱۰–۱۱: خطی که استثنا در آن رخ داده است؛
- خطوط ۱۵–۱۶: استثنای قبلی. یک استثنا میتواند یک استثنای دیگر را در بر بگیرد و بدین ترتیب یک پشته از استثناها را تعریف کند. ویژگی [previous] امکان استفاده از این پشته را فراهم میکند؛
7.10. بازانداختن یک استثنا
یک استثنا میتواند چندین بار پرتاب شود، همانطور که در اسکریپت زیر نشان داده شده است [exceptions-07.php]:
<?php
try {
try {
//یک استثنا پرتاب میشود
throw new \Exception("test");
} catch (\Exception $ex) {
// استثنای دریافتشده دوباره پرتاب میشود
throw $ex;
} finally {
// کد واقعاً وارد بلوک `finally` خواهد شد
print "finally 1\n";
}
} catch (\Exception $ex2) {
// استثنای اصلی با موفقیت بازیابی میشود
print $ex2->getMessage() . " dans try / catch / finally externe\n";
} finally {
// ما واقعاً وارد بلوک `finally` خواهیم شد
print "finally 2\n";
}
توضیحات
- خط ۶: یک استثنا پرتاب میشود؛
- خط ۷: استثنا گرفته میشود؛
- خط ۹: دوباره پرتاب میشود. سپس از بلوک try/catch/finally سطح بالا عبور میکند؛
- خط ۱۴: دوباره گرفته میشود؛
- خطوط ۱۰–۱۲: اجرای برنامه نشان میدهد که حتی پس از [throw] در خط ۹، کنترل در واقع به داخل بند [finally] از بلوک try/catch/finally منتقل میشود؛
نتایج
7.11. پردازش یک استک استثنا
یک استثنا میتواند یک استثنای دیگر را در بر بگیرد، که به نوبه خود میتواند استثنای دیگری را در بر بگیرد و در نهایت یک پشته از استثناها را تشکیل میدهد. در اینجا یک مثال [exceptions-08.php]:
نظرات
- خطوط ۴–۱۴: سه کلاس استثنا را که از استثنای از پیش تعریفشده [RuntimeException] مشتق شدهاند، تعریف کنید؛
- خط ۱۷: یک نمونه از کلاس [Exception3] در داخل یک نمونه از کلاس [Exception2] قرار گرفته است، که خود آن نیز در داخل یک نمونه از کلاس [Exception1] قرار گرفته است. سازندهٔ مورد استفاده در اینجا، سازندهٔ کلاس [Exception] است:
![]()
پارامتر سوم سازنده امکان جاسازی یک استثنا را فراهم میکند. این میتواند در سناریوی زیر مفید باشد:
- یک متد M تعریف شده است که ممکن است یک استثنا از نوع [Exception1] و فقط از این نوع را پرتاب کند، به دلایل سازگاری، برای مثال با یک رابط؛
- با این حال، ممکن است انواع دیگری از استثناها در داخل متد M رخ دهند. برای بازگرداندن یک خطا به کدی که متد M را فراخوانی کرده است، این استثناها سپس در نوع [Exception1] محصور شده و پرتاب میشوند. این کار تضمین میکند که اطلاعات موجود در استثنای محصورشده—که علت اصلی خطا بود—از بین نرود؛
- خطوط ۲۰–۲۷ نشان میدهند چگونه استک استثناهای تودرتو در یک استثنا را مدیریت کنیم؛
نتایج
object(Exception1)#1 (7) {
["message":protected]=>
string(11) "Erreur 1…"
["string":"Exception":private]=>
string(0) ""
["code":protected]=>
int(1)
["file":protected]=>
string(76) "C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-08.php"
["line":protected]=>
int(17)
["trace":"Exception":private]=>
array(0) {
}
["previous":"Exception":private]=>
object(Exception2)#2 (7) {
["message":protected]=>
string(11) "Erreur 2…"
["string":"Exception":private]=>
string(0) ""
["code":protected]=>
int(2)
["file":protected]=>
string(76) "C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-08.php"
["line":protected]=>
int(17)
["trace":"Exception":private]=>
array(0) {
}
["previous":"Exception":private]=>
object(Exception3)#3 (7) {
["message":protected]=>
string(11) "Erreur 3…"
["string":"Exception":private]=>
string(0) ""
["code":protected]=>
int(0)
["file":protected]=>
string(76) "C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-08.php"
["line":protected]=>
int(17)
["trace":"Exception":private]=>
array(0) {
}
["previous":"Exception":private]=>
NULL
}
}
}
Erreur 1…
Erreur 2…
Erreur 3…