الاستثناءات في PHP

من موسوعة حسوب
< PHP

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

يجب أن يكون كائن الاستثناء المرمي نسخة من الصنف Exception أو صنفًا متفرعًا منه، ويؤدي رمي كائن لا ينتمي إلى هذا الصنف إلى حدوث خطأ من نوع Fatal.

الكتلة catch

يمكن استخدام كتل catch متعدّدة لالتقاط أصناف مختلفة من الاستثناءات. تستمر عملية تنفيذ الشيفرة الاعتيادية (في حال عدم رمي أي استثناء في الكتلة block) بعد آخر كتلة catch معرّفة في الشيفرة. يمكن رمي الاستثناءات (أو إعادة رميها) ضمن الكتلة catch.

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

في الإصدار 7.1 وما بعده من اللغة، يمكن لكتلة catch أن تحدّد استثناءات متعددة باستخدام الرمز (|). هذه الميزة مفيدة عندما تتعامل اللغة بالطريقة نفسها مع استثناءات مختلفة من تسلسلات هرمية لأصناف مختلفة.

الكتلة finally

في الإصدار 5.5 من اللغة يمكن استخدام الكتلة block بعد أو بدلًا من كتل catch. تُنفّذ الشيفرة الموجودة ضمن الكتلة finally دائمًا بعد الشيفرة الموجودة في كتل try و catch بغض النظر عمّا إذا كان هناك استثناء مرمي، و قبل العودة إلى عملية تنفيذ الشيفرة الاعتيادية.

ملاحظات

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

نصيحة: تقدم مكتبة PHP القياسية (SPL) عددًا من الاستثناءات المدمجة المفيدة.

أمثلة

المثال 1: رمي استثناء

<?php
function inverse($x) {
    if (!$x) {
        throw new Exception('Division by zero.');
    }
    return 1/$x;
}

try {
    echo inverse(5) . "\n";
    echo inverse(0) . "\n";
} catch (Exception $e) {
    echo 'Caught exception: ',  $e->getMessage(), "\n";
}

// الاستمرار في عملية التنفيذ
echo "Hello World\n";
?>

يعطي المثال السابق المخرجات التالية:

0.2
Caught exception: Division by zero.
Hello World

المثال 2: التعامل مع الاستثناءات بواسطة كتلة finally

<?php
function inverse($x) {
    if (!$x) {
        throw new Exception('Division by zero.');
    }
    return 1/$x;
}

try {
    echo inverse(5) . "\n";
} catch (Exception $e) {
    echo 'Caught exception: ',  $e->getMessage(), "\n";
} finally {
    echo "First finally.\n";
}

try {
    echo inverse(0) . "\n";
} catch (Exception $e) {
    echo 'Caught exception: ',  $e->getMessage(), "\n";
} finally {
    echo "Second finally.\n";
}

// استمرار عملية التنفيذ
echo "Hello World\n";
?>

يعطي المثال السابق المخرجات التالية:

0.2
First finally.
Caught exception: Division by zero.
Second finally.
Hello World

المثال 3: استثناءات متداخلة

<?php

class MyException extends Exception { }

class Test {
    public function testing() {
        try {
            try {
                throw new MyException('foo!');
            } catch (MyException $e) {
                // رمي الاستثناء مرة أخرى
                throw $e;
            }
        } catch (Exception $e) {
            var_dump($e->getMessage());
        }
    }
}

$foo = new Test;
$foo->testing();

?>

يعطي المثال السابق المخرجات التالية:

string(4) "foo!"

المثال 4: كتل catch متعددة للتعامل مع الاستثناءات

<?php

class MyException extends Exception { }

class MyOtherException extends Exception { }

class Test {
    public function testing() {
        try {
            throw new MyException();
        } catch (MyException | MyOtherException $e) {
            var_dump(get_class($e));
        }
    }
}

$foo = new Test;
$foo->testing();

?>

يعطي المثال السابق المخرجات التالية:

string(11) "MyException"

توسيع الاستثناءات

يمكن تعريف صنف Exception خاص من قبل المستخدم وذلك بتوسيع الصنف Exception المُضمَّن في اللغة، ويوضّح المثال التالي التوابع والخواصّ المتاحة للاستخدام في الصنف الابن المُشتقّ من الصنف Exception الداخلي.

المثال 5: الصنف Exception الداخلي

<?php
class Exception
{
    protected $message = 'Unknown exception';
    // exception message
    private   $string;
    // __toString التقاط
    protected $code = 0; 
    // شيفرة الاستثناء المعرّفة من قبل المستخدم
    protected $file;
    // اسم الملف المصدري الخاص بالاستثناء
    protected $line; 
    // السطر المصدري الخاص بالاستثناء
    private   $trace; 
    // backtrace
    private   $previous; 
    // الصنف السابق في حال كان الاستثناء متداخلًا

    public function __construct($message = null, $code = 0, Exception $previous = null);

    final private function __clone();           
    // يمنع استنساخ الاستثناء

    final public  function getMessage();        
    // رسالة الاستثناء
    final public  function getCode();           
    // شيفرة الاستثناء
    final public  function getFile();           
    // اسم ملف المصدر
    final public  function getLine();           
    // سطر المصدر
    final public  function getTrace();          
    // مصفوفة التابع backtrace()
    final public  function getPrevious();      
    // الاستثناء السابق
    final public  function getTraceAsString();  
    // سلسلة نصّية منسقة trace

    // يمكن إعادة تعريفها
    public function __toString();
    // سلسلة نصية منسّقة لغرض العرض
}
?>

في حال وراثة أي صنف للصنف الداخلي Exception وإعادة تعريفه للتابع الباني، فينصح بشدّة استدعاء التابع الأب parent::__construct()‎ لضمان إسناد جميع البيانات المتاحة بصورة صحيحة. يمكن إعادة تعريف التابع ‎__toString()‎ لتقديم مخرجات خاصة عند عرض الكائن على هيئة سلسلة نصّية.

ملاحظة: لا يمكن استنساخ الاستثناءات، وتؤدي محاولة استنساخها إلى حدوث خطأ من نوع E_ERROR.

المثال 6: توسيع الصنف Exception في الإصدار 5.3.0 وما بعده من PHP

<?php
// تعريف صنف Exception خاص

class MyException extends Exception
{
إعادة تعريف الاستثناء لتصبح الرسالة معاملًا إلزاميًا
    public function __construct($message, $code = 0, Exception $previous = null) {
        // بعض الشيفرة هنا
    
        // التأكد من إسناد كل شيء بصورة صحيحة
        parent::__construct($message, $code, $previous);
    }

    // تمثيل الكائن على هيئة سلسلة نصّية ذات تنسيق خاص
    public function __toString() {
        return __CLASS__ . ": [{$this->code}]: {$this->message}\n";
    }

    public function customFunction() {
        echo "A custom function for this type of exception\n";
    }
}

// إنشاء صنف لاختبار الاستثناء


class TestException
{
    public $var;

    const THROW_NONE    = 0;
    const THROW_CUSTOM  = 1;
    const THROW_DEFAULT = 2;

    function __construct($avalue = self::THROW_NONE) {

        switch ($avalue) {
            case self::THROW_CUSTOM:
                // رمي استثناء خاص
                throw new MyException('1 is an invalid parameter', 5);
                break;

            case self::THROW_DEFAULT:
                // رمي استثناء اعتيادي
                throw new Exception('2 is not allowed as a parameter', 6);
                break;

            default: 
                // لا يوجد استثناء، وسيُنشئ الكائن
                $this->var = $avalue;
                break;
        }
    }
}


// المثال 1
try {
    $o = new TestException(TestException::THROW_CUSTOM);
} catch (MyException $e) {
      // سيُلتقط الاستثناء
    echo "Caught my exception\n", $e;
    $e->customFunction();
} catch (Exception $e) {
        // سيتم تجاوز الاستثناء
    echo "Caught Default Exception\n", $e;
}

// الاستمرار في تنفيذ الشيفرة
var_dump($o); // Null
echo "\n\n";


// المثال 2
try {
    $o = new TestException(TestException::THROW_DEFAULT);
} catch (MyException $e) {
      // غير مطابق لهذا النوع
    echo "Caught my exception\n", $e;
    $e->customFunction();
} catch (Exception $e) {
        // سيلتقط الاستثناء
    echo "Caught Default Exception\n", $e;
}

// الاستمرار في تنفيذ الشيفرة
var_dump($o); // Null
echo "\n\n";


// المثال 3
try {
    $o = new TestException(TestException::THROW_CUSTOM);
} catch (Exception $e) {
        // سيلتقط الاستثناء
    echo "Default Exception caught\n", $e;
}

// الاستمرار في تنفيذ الشيفرة
var_dump($o); // Null
echo "\n\n";


// المثال 4
try {
    $o = new TestException();
} catch (Exception $e) {
// ستتجاوز اللغة هذه العبارة نظرًا لعدم وجود استثناء
    echo "Default Exception caught\n", $e;
}

// الاستمرار في تنفيذ الشيفرة
var_dump($o); // TestException
echo "\n\n";
?>

ملاحظة: لا تدعم الإصدارات السابقة للإصدار 5.3.0 تداخل الاستثناءات، ويمكن استخدام الشيفرة التالية كبديل عن الصنف MyException إن كنت ترغب في تطبيق هذا المثال.

<?php
// تعريف صنف استثناء خاص
class MyException extends Exception
{
// إعادة تعريف الاستثناء بحيث يصبح المعامل الأول إلزاميًا


    public function __construct($message, $code = 0) {
        // بعض الشيفرة هنا
    
        // التأكد من أن جميع عمليات الإسناد قد تمت بصورة صحيحة
        parent::__construct($message, $code);
    }

    // تمثيل الكائن على هيئة سلسلة نصّية ذات تنسيق خاص

    public function __toString() {
        return __CLASS__ . ": [{$this->code}]: {$this->message}\n";
    }

    public function customFunction() {
        echo "A custom function for this type of exception\n";
    }
}
?>

مصادر