Exception در لغت به معنای «استثناء» است اما در زبانهای برنامهنویسی از یک دید کلی میتوان آنها را همچون ارورها تلقی کرد به طوری که وقتی ایجاد میشوند، ادامهٔ روند اجرای برنامه متوقف میگردد. از نسخهٔ PHP 5.0 به بعد قابلیتی به این زبان اضافه شد که از آن طریق توسعهدهندگان این زبان میتوانند یک اِکسپشن را به اصطلاح Throw سپس Catch کنند. به بیانی قابلفهمتر، این قابلیت در زبان پیاچپی گنجانده شده تا بر اساس الگوریتم مد نظر توسعهدهنده، وی بتوانند در صورت نیاز و در شرایطی خاص Throw Exception کند؛ یعنی یک اِکسپشن ایجاد کرده و پیامی داخل آن درج نموده و دلیل وقوع آن را توضیح دهد سپس در جایی دیگر از کد Catch Exception کند بدان معنا که اِکسپشن را دریافت نموده و پیامش را در معرض دید کاربر قرار دهد.
در حقیقت، اِکسپشنها این امکان را در اختیارمان قرار میدهند تا بتوانیم در صورتی که چیزی به خطا رفت، به سادگی جریان اجرای برنامه را متوقف نموده و یا برنامه را ادامه داده اما پیامی در معرض دید کاربر قرار داد مضاف بر این که از طریق اِکسپشنها میتوانیم نحوهٔ هندل کردن ارورها را به هر شکلی که بخواهیم مدیریت کنیم. اساساً وقتی که اِکسپشنی رخ میدهد، روند اجرای برنامه متوقف میگردد مگر آن که به شیوهای کدنویسی کرده باشیم که آن اِکسپشن به اصطلاح Catch گردد.
هشدار اِکسپشنهای مدیریت نشدهای که وقتی نرمافزار روی سرورهای اصلی دیپلوی شد رخ میدهند، منجر به توقف کامل نرمافزار شده و بالتبع تجربهٔ کاربری بدی را برای کاربران رقم خواهند زد.اِکسپشنها بخشی از OOP در زبان پیاچپی هستند به طوری که بر آن اساس میتوان از کلاسهای مخصوص این کار که در هستهٔ این زبان گنجانده شده ارثبری نموده و کلاسهای کاستومایزشدهای برای هندل کرد ارورها ساخت.
آشنایی با کلاس Exception
در زبان پیاچپی یک کلاس پایهای داریم تحت عنوان Exception
که به منظور مدیریت اِکسپشنها مورد استفاده قرار میگیرد. به منظور درک بهتر سازوکار این کلاس، داخل پوشهٔ oop
پروژهای تحت عنوان exception-handling
ساخته و ساختار پروژهای که در این دورهٔ آموزشی از ابتدا مورد استفاده قرار دادیم را داخل آن ایجاد میکنیم. برای شروع، داخل پوشهٔ classes
فایلی به نام User.php
ساخته و آن را به صورت زیر تکمیل میکنیم:
<?php
namespace SokanAcademy;
class User
{
public function isDeveloper(bool $value)
{
if ($value == true) {
return "This user is a developer.";
} else {
throw new Exception("EXCEPTION: This user is not a developer.");
}
}
}
داخل این کلاس متدی نوشتهایم به نام ()isDeveloper
که اساساً چک میکند ببیند که آیا آبجکت ساختهشده از روی این کلاس یک توسعهدهنده است یا خیر. این متد یک پارامتر ورودی میگیرد تحت عنوان value$
که تایپ هینت آن bool
است بدان معنا که آرگومانهای انتخابی برای این متد فقط و فقط میتوانند true
یا false
باشند. داخل بدنهٔ این متد با استفاده از یک دستور شرطی چک کردهایم ببینیم که آیا مقدار پارامتر ورودی برابر با true
است یا خیر که اگر این گونه بود، وارد بلوک if
شده و استرینگی مبنی بر این که آبجکت ساختهشده از روی این کلاس یک دولوپر است ریترن میکنیم و در غیر این صورت نیز وارد بلوک else
شده و با استفاده از کیورد throw
آبجکتی از روی کلاس Exception
پیاچپی ساخته و استرینگی را به عنوان پارامتر ورودی این کلاس در نظر میگیریم. حال وارد فایل index.php
شده و آن را به صورت زیر تکمیل میکنیم:
<?php
ini_set('display_errors', '1');
require_once 'vendor/autoload.php';
$user = new SokanAcademy\User();
echo $user->isDeveloper(false);
echo "\n";
echo "The rest of the program.";
همانطور که میبینیم، از روی کلاس User
آبجکتی ساختهایم به نام user$
سپس در خط بعد متد ()isDeveloper
را به آن منتسب نموده و نتیجه را چاپ کردهایم به طوری که اگر این فایل را در محیط کامندلاین اجرا کنیم، به عنوان خروجی خواهیم داشت:
/var/www/oop/exception-handling$ php index.php
PHP Fatal error: Uncaught Error: Class 'SokanAcademy\Exception' not found in /var/www/oop/exception-handling/classes/User.php:11
اساساً وقوع این ارور خارج از نکات آموزشی موضوع مورد بحث در این آموزش است و میتوان گفت صرفاً به دلیل بیدقتی در توسعهٔ کلاس User
رخ داده است! به عبارتی، متن این ارور حاکی از آن است که کلاسی تحت عنوان Exception
داخل کلاس User
به رسمیت شناخته نشده است.
پیش از این توضیح دادیم که وقتی از نِیماِسپیس استفاده میکنیم، کلاسهایی که در هستهٔ زبان پیاچپی تعبیه شدهاند را میباید با درج علامت \
مورد استفاده قرار دهیم که در غیر این صورت، با ارور مواجه خواهیم شد و درج این علامت موجب میگردد که مفسر پیاچپی نِیماِسپیسی که داخلش قرار داریم را نادیده بگیرد. برای همین منظور، کلاس User
را به صورت زیر اصلاح میکنیم:
<?php
namespace SokanAcademy;
class User
{
public function isDeveloper(bool $value)
{
if ($value == true) {
return "This user is a developer.";
} else {
throw new \Exception("EXCEPTION: This user is not a developer.");
}
}
}
حال مجدد فایل index.php
را اجرا میکنیم:
/var/www/oop/exception-handling$ php index.php
PHP Fatal error: Uncaught Exception: This user is not a developer. in /var/www/oop/exception-handling/classes/User.php:11
میبینیم که مجدد به ارور برخوردیم اما این بار ارور فوق همانی است که خودمان دست به ایجادش زدهایم و اگر به متن ارور توجه کنیم، میبینیم که دقیقاً همان استرینگی که به عنوان آرگومان ورودی کلاس Exception
در نظر گرفته بودیم چاپ شده است با این توضیح که در متن ارور چیزی تحت عنوان «Uncaught Exception» نیز چاپ شده است بدان معنا که ارور یا اِکسپشن فوق Catch یا Handle و یا بهتر بگوییم مدیریت نشده است. نکتهای که در ارتباط با اجرای کد فوق وجود دارد آن است که متنی که در خط هشتم نوشتهایم هرگز چاپ نشده است زیرا پیش از این گفتیم وقتی که اِکسپشنی رخ میدهد، اگر آن را هندل نکنیم روند اجرای برنامه متوقف شده و از آن نقطهای که اِکسپشن رخ داده به بعد دیگر اجرا نخواهد شد و همین مسئله لزوم هندل کردن اِکسپشنها را نشان میدهد.
در زبان برنامهنویسی پیاچپی کلیدواژگانی داریم تحت عنوان try
و catch
که به منظور مدیریت اِکسپشنها مورد استفاده قرار میگیرند که برای روشنتر شدن نحوهٔ کارکرد آنها، فایل index.php
را به صورت زیر تکمیل میکنیم:
<?php
ini_set('display_errors', '1');
require_once 'vendor/autoload.php';
$user = new SokanAcademy\User();
try {
echo $user->isDeveloper(true);
} catch(Exception $e) {
echo $e->getMessage();
}
echo "\n";
echo "The rest of the program.";
ابتدا خروجی کدهای فوق را مشاهده نموده سپس به تفسیر آنها خواهیم پرداخت:
/var/www/oop/exception-handling$ php index.php
This user is a developer.
The rest of the program.
با توجه به این که آرگومان true
را برای متد ()isDeveloper
در نظر گرفتهایم، صرفاً کدهای داخل بلوک if
این متد اجرا شده و هیچ گونه اِکسپشنی ایجاد نخواهد شد و میبینیم که استرینگ مرتبط با خط دوازدهم (خط هشتم در نمونهٔ قبل) به درستی چاپ شده است.
در توضیح کدهای فوق میتوان گفت آن بخشی از کد که قصد داریم اجرایش کنیم را داخل بلوک try
قرار دادهایم که این وظیفه را دارا است تا طبق روال معمول کدها را اجرا کند. اگر کد مشکلی نداشته باشد و اساساً هیچ گونه اِکسپشنی رخ ندهد، پس از اِتمام اجرای کدهای داخل این بلوک، از ساختار try
و catch
خارج شده و ادامهٔ کدها شروع به اجرا میکنند و همانطور که میبینیم، خط دوازدهم هم اجرا شده است اما اگر در حین توسعهٔ متد ()isDeveloper
یک اِکسپشن به اصطلاح Throw کرده باشیم، بلافاصله وارد بلوک catch
خواهیم شد به طوری که برای تست این موضوع، کدهای داخل فایل index.php
را به صورت زیر تغییر میدهیم:
<?php
ini_set('display_errors', '1');
require_once 'vendor/autoload.php';
$user = new SokanAcademy\User();
try {
echo $user->isDeveloper(false);
} catch (Exception $e) {
echo $e->getMessage();
}
echo "\n";
echo "The rest of the program.";
تنها کاری که انجام دادهایم آن است که برای متد ()isDeveloper
به جای آرگومان true
از false
استفاده نمودهایم و از این پس در خروجی خواهیم داشت:
/var/www/oop/exception-handling$ php index.php
EXCEPTION: This user is not a developer.
The rest of the program.
همانطور که میبینیم، به دلیل این که از مقدار false
استفاده کردهایم، داخل متد ()isDeveloper
دستور دادهایم تا وارد بلوک else
شده و در آنجا هم یک اِکسپشن ایجاد کردهایم و سازوکار ساختار try
و catch
بدین شکل است که اگر در خروجی متد مذکور اِکسپشن ایجاد شده باشد، وارد بلوک catch
خواهد شد. همانطور که ملاحظه میشود، داخل پرانتزهای مرتبط با catch
آبجکتی تحت عنوان e$
یا هر نام دلخواه دیگری از روی کلاس Exception
ساختهایم و این کلاس متدی دارد تحت عنوان ()getMessage
که حاوی پیامهای مرتبط با اِکسپشنها است و داخل بلوک catch
هم خروجی این متد را چاپ کردهایم و از آنجا که قبلاً داخل کلاس User
پیامی همچون «.EXCEPTION: This user is not a developer» برای آبجکت ساختهشده از روی کلاس Exception
در نظر گرفتهایم، مسلماً داخل بلوک catch
به آن دسترسی خواهیم داشت.
آنچه در ارتباط با کدهای فوق قابلتأمل است این که میبینیم وقوع اِکسپشن هرگز منجر به توقف برنامه نشده و نه تنها تجربهٔ کاربری بدی برای کاربر رقم نخواهد خورد، بلکه اگر متن ارور گویا باشد وی خواهد دانست که در گام بعدی چه کاری باید انجام دهد.
علاوه بر متد ()getMessage
یک سری متد دیگر داخل کلاس Exception
برای مصارف مختلف گنجانده شدهاند که عبارتند از:
final public getMessage (void) : string
final public getPrevious (void) : Throwable
final public getCode (void) : mixed
final public getFile (void) : string
final public getLine (void) : int
final public getTrace (void) : array
final public getTraceAsString (void) : string
طور مثال، از متد ()getFile
به صورت زیر میتوان استفاده نمود تا آدرس فایلی که اِکسپشن در آن درخ داده است را نشان داد:
<?php
ini_set('display_errors', '1');
require_once 'vendor/autoload.php';
$user = new SokanAcademy\User();
try {
echo $user->isDeveloper(false);
} catch (Exception $e) {
echo $e->getFile();
}
echo "\n";
echo "The rest of the program.";
به عنوان خروجی هم خواهیم داشت:
/var/www/oop/exception-handling/classes/User.php
The rest of the program.
میبینیم که خروجی متد ()getFile
یک استرینگ است حاوی آدرس فایلی که اِکسپشن مذکور داخل آن رخ داده است (اگر متد ()getLine
را فراخوانی کنیم، میبینیم که به درستی خروجی ۱۱ را نشان میدهد که مرتبط با خط یازدهمی داخل فایل User.php
است که اِکسپشن فوق رخ داده است.)
اساساً Exception Handling یکی از بخشهای لاینفک شیئگرایی در زبان پیاچپی است به طوری که با ارثبری از کلاس پایهٔ Exception
میتوان برای بخشهای مختلف نرمافزار اِکسپشنهای کاستومایزشدهای نوشته و در موقعیت مناسب از آنها استفاده نمود که در ادامهٔ این آموزش به بررسی دقیقتر این موضوع خواهیم پرداخت.
آشنایی با نحوهٔ ارثبری از کلاس Exception
با نگاه به مستندات کلاس Exception در سایت رسمی پیاچپی، میبینیم که میشود گفت تقریباً تمامی متدهای نوشتهشده داخل این کلاس از جنس final
هستند و از همین روی امکان اُورراید کردن آنها را نخواهیم داشت؛ اما علاوه بر استفاده از آنها، میتوانیم متدهای اختصاصی خودمان را در کنار آنها بیفزاییم که برای درک بهتر چگونگی ارثبری از کلاس پایهٔ Exception
، داخل پوشهٔ classes
فایلی میسازیم تحت عنوان MyCustomExceptionClass.php
و کدهای زیر را داخل آن مینویسیم:
<?php
namespace SokanAcademy;
class MyCustomExceptionClass extends \Exception
{
protected $message;
protected $code;
public function __construct($message, $code)
{
$this->message = $message;
$this->code = $code;
}
public function __toString()
{
return "$this->message with code of " . $this->code;
}
}
همانطور که ملاحظه میشود، نامی دلخواه همچون MyCustomExceptionClass
برای این کلاس در نظر گرفتهایم که کلیهٔ خصوصیاتش را از کلاس اصلی Exception
به ارث برده است. داخل بدنهٔ این کلاس دو پراپرتی از جنس protected
تحت عناوین message$
و code$
ساختهایم که فقط و فقط از داخل همین کلاس و کلاسهای فرزند این کلاس به آنها دسترسی خواهیم داشت و از همین روی داخل کانستراکتور آنها را مقداردهی کردهایم.
در آموزش آشنایی با متدهای به اصطلاح Magic در زبان PHP با دو مورد از مَجیک متدهای پیاچپی آشنا شدیم و در این آموزش با مَجیک متد دیگری تحت عنوان ()toString__
آشنا خواهیم شد که این وظیفه را دارا است تا به محض ساخت کلاسی که این متد داخلش فراخوانی شده، پراپرتیهای این کلاس را در قالب یک استرینگ در معرض دیدمان قرار میدهد مضاف بر این که استفاده از این مَجیک متد اجازه میدهد تا یک آبجکت را همچون یک متغیری که حاوی مقدار استرینگ است با استفاده از دستوراتی همچون print
و echo
چاپ کنیم. داخل متد ()toString__
دستور دادهایم تا مقادیر پراپرتیهای message$
و code$
چاپ شوند.
حال جهت تست، کلاس User
را به صورت زیر تغییر میدهیم به گونهای که از این پس به جای استفاده از کلاس پایهٔ Exception
از کلاس اختصاصی خودمان تحت عنوان MyCustomExceptionClass
استفاده خواهیم کرد:
<?php
namespace SokanAcademy;
class User
{
public function isDeveloper(bool $value)
{
if ($value == true) {
return "This user is a developer.";
} else {
throw new MyCustomExceptionClass("EXCEPTION: This user is not a developer.", 100);
}
}
}
همانطور که ملاحظه میشود، دو پارامتر الزامی این کلاس در نظر گرفته شدهاند مضاف بر این که با توجه به قرار داشتن کلاس MyCustomExceptionClass
در نِیماِسپیس SokanAcademy
، همچون مورد قبل دیگری نیاز به درج علامت \
نخواهیم داشت. حال وارد فایل index.php
شده و آن را به صورت زیر ریفکتور میکنیم:
<?php
ini_set('display_errors', '1');
require_once 'vendor/autoload.php';
$user = new SokanAcademy\User();
try {
echo $user->isDeveloper(false);
} catch (SokanAcademy\MyCustomExceptionClass $myCustomExceptionObj) {
echo $myCustomExceptionObj;
}
echo "\n";
echo "The rest of the program.";
داخل پرانتزهای بلوک catch
آدرس کامل کلاس مذکور را نوشته و آن را در متغیری به نام myCustomExceptionObj$
ذخیره ساختهایم و داخل بلوک catch
نیز بدون فراخوانی هیچ گونه متدی، صرفاً این آبجکت را با استفاده از دستور echo
چاپ کردهایم زیرا گفتیم که مَجیک متد ()toString__
این وظیفه را دارا است تا با آبجکتها رفتاری همچون یک استرینگ داشته باشد. در صورتی که این فایل را در مرورگر یا محیط کامندلاین اجرا کنیم، در خروجی شاهد خواهیم بود:
/var/www/oop/exception-handling$ php index.php
EXCEPTION: This user is not a developer. with code of 100
The rest of the program.
میبینیم که به درستی اِکسپشنی که به اصطلاح Throw کرده بودیم داخل این فایل Catch شده است.
آشنایی با نحوهٔ بهکارگیری چند بلوک catch
یک از قابلیتهای زبان پیاچپی این است که در آنِ واحد میتوان از چند بلوک catch
به منظور هندل کردن بیش از یک نوع اِکسپشن استفاده نمود که برای روشنتر شدن این مسئله، کلاس User
را به صورت زیر تکمیل میکنیم:
<?php
namespace SokanAcademy;
class User
{
public function isDeveloper(bool $value)
{
if ($value == true) {
return "This user is a developer.";
} else {
throw new MyCustomExceptionClass("EXCEPTION: This user is not a developer.", 100);
}
}
public function throwMultipleExceptionTypes($input)
{
if (is_string($input)) {
return "The input is a string.";
} else {
if (is_int($input)) {
throw new MyCustomExceptionClass("EXCEPTION from MyCustomExceptionClass: The input is an integer", 101);
} else if (is_array($input)) {
throw new \Exception("EXCEPTION from Exception class: The input is an array.");
}
}
}
}
متدی ساختهایم به نام ()throwMultipleExceptionTypes
که یک پارامتر ورودی میگیرد تحت عنوان input$
و داخل بدنهٔ این متد چک کردهایم که اگر مقدار این پارامتر استرینگ بود، وارد بدنهٔ اولین دستور شرطی میشویم و استرینگی را چاپ میکنیم و در غیر این صورت وارد بلوک else
شده سپس داخل این بلوک مجدد چک میکنیم ببینیم که آیا مقدار این پراپرتی عدد صحیح است یا خیر که اگر این گونه بود، یک اِکسپشن از جنس MyCustomExceptionClass
تولید میکنیم و اگر هم مقدار پراپرتی input$
برابر با یک آرایه بود، اِکسپشنی از جنس کلاس پایهٔ Exception
خودِ پیاچپی ایجاد میکنیم. به منظور تست این متد، وارد فایل index.php
شده و آن را به صور زیر تغییر میدهیم:
<?php
ini_set('display_errors', '1');
require_once 'vendor/autoload.php';
$user = new SokanAcademy\User();
// try {
// echo $user->isDeveloper('ali');
// } catch (SokanAcademy\MyCustomExceptionClass $myCustomExceptionObj) {
// echo $myCustomExceptionObj;
// }
try {
echo $user->throwMultipleExceptionTypes('something');
} catch (SokanAcademy\MyCustomExceptionClass $myCustomExceptionObj) {
echo $myCustomExceptionObj;
} catch (Exception $e) {
echo $e->getMessage();
}
echo "\n";
echo "The rest of the program.";
ابتدا کدهای قبلی را کامنت نموده سپس داخل بلوک try
متد جدیدی که نوشتیم را کال کردهایم و یک استرینگ به عنوان آرگومان ورودی این متد در نظر گرفتهایم. اگر این فایل را اجرا کنیم، در خروجی خواهیم داشت:
/var/www/oop/exception-handling$ php index.php
The input is a string.
The rest of the program.
حال به عنوان آرگومان متد ()throwMultipleExceptionTypes
از یک عدد صحیح همچون 7 استفاده میکنیم و مجدد این فایل را اجرا میکنیم:
/var/www/oop/exception-handling$ php index.php
EXCEPTION from MyCustomExceptionClass: The input is an integer with code of 101
The rest of the program.
میبینیم که بلوک catch
اول اجرا شد و اِکسپشنی که از جنس MyCustomExceptionClass
ایجاد شده بود دریافت شد و این در حالی است که اگر مقداری همچون یک آرایه به عنوان آرگومان ورودی این متد در نظر بگیریم، در خروجی خواهیم داشت:
/var/www/oop/exception-handling$ php index.php
EXCEPTION from Exception class: The input is an array.
The rest of the program.
این بار وارد بلوک دوم catch
شدیم چرا که آرگومان ورودی از جنس آرایه بود و داخل بدنهٔ متد ()throwMultipleExceptionTypes
نیز دستور دادهایم که اگر این حالت صورت گرفت، اِکسپشنی از روی کلاس Exception
ایجاد گردد.
آشنایی با کاربرد بلوک finally
پس از بلوک try
، میتوان از بلوک finally
به جای بلوک catch
و یا پس از آن استفاده نمود با این توضیح که هر کدی داخل این بلوک قرار گیرد، خواه اِکسپشنی رخ دهد و وارد بلوک catch
شویم و خواه کدهای داخل بلوک try
بدون هیچ گونه مشکلی اجرا شوند، هر دستوری که داخل بلوک finally
نوشته باشیم اجرا خواهد شد. به طور مثال داریم:
<?php
ini_set('display_errors', '1');
require_once 'vendor/autoload.php';
$user = new SokanAcademy\User();
// try {
// echo $user->isDeveloper('ali');
// } catch (SokanAcademy\MyCustomExceptionClass $myCustomExceptionObj) {
// echo $myCustomExceptionObj;
// }
try {
echo $user->throwMultipleExceptionTypes([]);
} catch (SokanAcademy\MyCustomExceptionClass $myCustomExceptionObj) {
echo $myCustomExceptionObj;
} catch (Exception $e) {
echo $e->getMessage();
} finally {
echo "\n";
echo "Any code in here will execute all the time.";
}
echo "\n";
echo "The rest of the program.";
در صورتی که فایل index.php
را مجدد در محیط کامندلاین اجرا کنیم، شاهد خروجی زیر خواهیم بود:
/var/www/oop/exception-handling$ php index.php
EXCEPTION from Exception class: The input is an array.
Any code in here will execute all the time.
The rest of the program.
در پاسخ به این پرسش که «بلوک finally
در شرایط واقعی چه کاربردی دارا است؟» میتوان گفت وقتی که با دیتابیس یک کانکشن برقرار میسازیم، خواه این ارتباط موفقیتآمیز بوده باشد و وارد بلوک try
شویم و خواهم به مشکل خورده و وارد بلوک catch
شده باشیم، در نهایت میتوان داخل بلوک finally
دستور داد که ارتباط با دیتابیس قطع گردد تا برای ریکوئستهای بعد هیچ گونه کانکشن بازی وجود نداشته باشد و سیستم به راحتی بتواند کانکشن جدیدی ایجاد نماید.
جمعبندی
زمانی که پای توسعهٔ نرمافزار به میان میآید، یکی از بخشهای لاینفک این پروسه ایجاد سهوی باگهایی است که در نهایت منجر به بروز یک سری ارور یا اِکسپشن میشوند و ما به عنوان برنامهنویس وظیفه داریم که تا حد ممکن آنها را به بهترین شکل مدیریت کنیم. در همین راستا، در این آموزش به بررسی کلاس Exception
زبان برنامهنویسی پیاچپی پرداخته و در ادامه دیدیم که به چه شکل میتوان بسته به نیازهای نرمافزاری خود اقدام به ساخت کلاسهای مدیریت اِکسپشن شخصیسازیشدهای پرداخت.