Kodokon kodokon.com

الاستثناءات: التسلسل الهرمي، finally، المعالج الشامل

ابنِ استراتيجية أخطاء كاملة: استثناءات عمل متسلسلة، وfinally منضبط، ومعالِجات شاملة كخط دفاع أخير.

9 دقيقة · 3 أسئلة

افتح هذا الدرس في Kodokon

منذ PHP 7، كل ما يُطلَق ينفّذ Throwable، بفرعين: Error (إخفاقات المحرّك والأنواع، مثل TypeError) وException (إخفاقات التطبيق). ولا تلتقط Error إلا عند حدود البرنامج. أما على صعيد التطبيق، فتوفّر مكتبة SPL تسلسلًا هرميًا جاهزًا: يشير LogicException إلى خلل من المطوّر، يجب إصلاحه؛ ويشير RuntimeException إلى طارئ وقت التشغيل، يجب معالجته. ورد الفعل المهني: اشتقّ استثناءات العمل لديك من هذه القواعد، استثناءً واحدًا لكل حالة يمكن تحديدها.

PHP
<?php

declare(strict_types=1);

abstract class AppException extends RuntimeException
{
}

final class UserNotFound extends AppException
{
    public static function withEmail(
        string $email
    ): self {
        return new self("No user for $email");
    }
}

try {
    throw UserNotFound::withEmail('lea@example.com');
} catch (UserNotFound $e) {
    echo $e->getMessage();
}
تسلسل هرمي للعمل: تلتقط UserNotFound أو AppException أو RuntimeException بحسب المستوى.

لا قيمة لـ try/catch إلا إذا كنت تعرف ما تفعله بالخطأ - وإلا، فدعه يصعد إلى الأعلى. وثلاث أدوات تصقل التحكّم: الالتقاط المتعدّد catch (JsonException | ValueError $e) يجمع المعالجة المتطابقة؛ ويربط الوسيط previous السبب الأصلي بسلسلة عندما تترجم استثناءً تقنيًا إلى استثناء عمل؛ وتُنفَّذ finally في كل الأحوال - النجاح، والاستثناء، وحتى return مبكر - وهي مثالية لتحرير مورد.

PHP
<?php

declare(strict_types=1);

final class PayloadInvalid extends RuntimeException
{
}

function decode(string $json): mixed
{
    try {
        return json_decode(
            $json,
            true,
            flags: JSON_THROW_ON_ERROR,
        );
    } catch (JsonException $e) {
        throw new PayloadInvalid(
            'Invalid JSON payload',
            previous: $e,
        );
    } finally {
        echo "decode() finished\n";
    }
}

try {
    decode('{broken');
} catch (PayloadInvalid $e) {
    echo $e->getPrevious()?->getMessage();
}
ترجمة الاستثناء: يبقى السبب التقني في المتناول عبر getPrevious().

في نهاية السلسلة، يُغلق معالِجان شاملان الإعداد. يلتقط set_exception_handler كل ما لم يُعالَج: تسجيل مُنظَّم، واستجابة 500 عامة - دون كشف الرسالة أو التتبّع (trace) للمستخدم أبدًا. ويحوّل set_error_handler الأخطاء القديمة (التحذيرات، والإشعارات) إلى ErrorException، موحِّدًا العالَمين: فيصبح كل شيء استثناءً، ويتّبع كل شيء مسار المعالجة نفسه.

PHP
<?php

declare(strict_types=1);

set_error_handler(function (
    int $severity,
    string $message,
    string $file,
    int $line,
): bool {
    throw new ErrorException(
        $message,
        0,
        $severity,
        $file,
        $line,
    );
});

set_exception_handler(function (Throwable $e): void {
    error_log($e->getMessage());
    http_response_code(500);
    echo 'A technical error occurred.';
});

trigger_error('Legacy warning', E_USER_WARNING);
echo 'Never reached';
يصبح التحذير استثناءً، غير مُلتقَط: فيستجيب المعالج الشامل، ثم يتوقف السكربت.

اختبار المعرفة

تأكّد من أنك تذكّرت النقاط الأساسية في هذا الدرس.

  1. ما القاسم المشترك بين Error وException في PHP الحديثة؟
    • يرث Error من Exception
    • كلاهما ينفّذ الواجهة Throwable
    • كلاهما محجوز لمحرّك PHP
  2. متى تُنفَّذ كتلة finally؟
    • فقط إذا لم يُطلَق أيّ استثناء
    • فقط بعد التقاط (catch)
    • في كل الأحوال: النجاح، والاستثناء المُلتقَط أو غير المُلتقَط، والإرجاع المبكر
  3. ما وظيفة الوسيط previous في الاستثناء؟
    • الحفاظ على الاستثناء الأصلي عندما تترجمه إلى استثناء أعلى مستوى
    • منع الاستثناء من الصعود عبر مكدس الاستدعاء
    • تحديد الاستثناء الذي يُطلَق عند الفشل التالي