معالجة الأخطاء

أي الاستدعاءات ترمي استثناءات وأيها لا يرمي أبداً، وتسلسل الاستثناءات، وقيم ErrorCode الثابتة، وكيفية التقاط الإخفاقات وتسجيلها بأمان.

في هذه الصفحة
  1. أسلوبان للإخفاق
  2. تسلسل الاستثناءات
  3. رموز الأخطاء
  4. قراءة الرمز والسياق
  5. أنماط الالتقاط
  6. ما لا يرمي أبدًا
  7. سقوف المدخلات
  8. قاعدة نطاق التقويم
  9. مثال كامل

أسلوبان للإخفاق#

تستخدم كل دالة في RTLY-Kit أحد أسلوبين فقط، فتعرف دائمًا ما الذي يجب حمايته:

  • أدوات التحقق لا ترمي استثناءً أبدًا. NationalCode وSheba وBankCard وMobile وPostalCode وVehiclePlate (ومعها الدوال المساعدة is_* / validate_*) تقبل أي قيمة وتجيب بـ false أو بـ Result غير صالح. راجع نظرة عامة على أدوات التحقق.
  • كل ما عدا ذلك يرمي RtlyKit\Exceptions\RtlyKitException (أو صنفًا فرعيًا منه) عند مدخل لا يستطيع قبوله. ولا يتسرب TypeError أو ValueError أو استثناء DateMalformed* من PHP بسبب مدخل خاطئ؛ فإن حدث ذلك فهو خلل في المكتبة، نرجو الإبلاغ عنه.

تسلسل الاستثناءات#

\InvalidArgumentException
 └─ RtlyKit\Exceptions\RtlyKitException        implements RtlyKitThrowable
     ├─ InvalidDateException
     ├─ InvalidNumberException
     ├─ InvalidPrayerConfigException
     └─ UnsupportedLocaleException

RtlyKitThrowable واجهة علامة (marker interface) تعلن عن getErrorCode(): ErrorCode وgetContext(): array. التقطها (أو التقط RtlyKitException) لمعالجة المكتبة كلها بعبارة واحدة. ولأن RtlyKitException ترث \InvalidArgumentException، فإن الشيفرة الحالية التي تستخدم catch (\InvalidArgumentException) وcatch (\LogicException) تبقى تعمل.

الاستثناءيُرمى عند
InvalidDateExceptionتواريخ مستحيلة، وسنوات خارج النطاق المدعوم، ونصوص فارغة أو لا يمكن تحليلها، وقيم add*() / sub*() غير معقولة، وطوابع زمنية خارج النطاق، وأنماط تنسيق طويلة جدًا
InvalidNumberExceptionمدخل غير رقمي أو كسري، وNaN / INF، ومدخل ضخم الحجم، وأعداد أكبر من المسموح، وكلمات أعداد غير معروفة
InvalidPrayerConfigExceptionطريقة حساب صلاة غير معروفة، أو مدينة غير معروفة، أو معامل عصر غير صالح
UnsupportedLocaleExceptionلغة (locale) لا تدعمها الدالة (مثل number_to_words(5, 'de'))
RtlyKitException نفسهاوسائط أخرى خاطئة، مثل فاصل Slugify::make() غير صالح أو طويل جداً، أو نص غير صالح بترميز UTF-8، أو جدول بيانات مضمَّن مفقود

رموز الأخطاء#

رسائل الاستثناءات موجهة للبشر وقد تُعاد صياغتها في أي إصدار. أما رمز الخطأ فهو الجزء الثابت: وهو enum نصي من النوع ErrorCode، وأسماء حالاته وقيمها جزء من الواجهة العامة (راجع استقرار الواجهة البرمجية).

الحالةالقيمةالمعنى
InvalidArgumentinvalid_argumentرمز احتياطي عام لوسيط غير صالح لا يوجد له رمز أكثر تحديدًا
InvalidDateinvalid_dateتاريخ أو وقت مشوّه أو مستحيل أو لا يمكن تحليله (شهر 13، أو 30 اسفند في سنة غير كبيسة، أو 'not a date'، أو نص فارغ)
DateOutOfRangedate_out_of_rangeسنة أو تاريخ أو طابع زمني أو قيمة add*() / sub*() خارج النطاق المدعوم (راجع الحدود)
InvalidNumberinvalid_numberلا يمكن قراءة القيمة كعدد (صحيح)
NumberTooLargenumber_too_largeعدد أكبر من الحجم المدعوم
NonFiniteNumbernon_finite_numberNaN أو INF حيث يلزم عدد منتهٍ
InvalidNumberWordsinvalid_number_wordsكلمات أعداد غير معروفة أو غير سليمة في NumberToWords::fromWords()
InputTooLonginput_too_longمدخل نصي يتجاوز حدًّا موثَّقًا للحجم
InvalidPrayerConfiginvalid_prayer_configمدينة أو طريقة حساب أو معامل عصر غير معروف
UnsupportedLocaleunsupported_localeلغة (locale) غير اللغات المدعومة
DataUnavailabledata_unavailableجدول بيانات مضمَّن مفقود أو تالف (مشكلة في التحزيم وليست خطأ من المستخدم)
ملاحظة. النصوص التي تُرجعها Result::errors() (invalid_format وinvalid_checksum وinvalid_type ...) نصوص عادية خاصة بأدوات التحقق وليست حالات من ErrorCode. فالـ enum مخصص للاستثناءات فقط.

قراءة الرمز والسياق#

لكل استثناء في المكتبة الدالتان getErrorCode() وgetContext(). السياق مصفوفة من الحقائق المقروءة آليًا عن الإخفاق (حدّ، أو اللغة المخالفة، أو اسم الوسيط). ولا يحتوي أسرارًا أبدًا وقد يكون فارغًا. يمرّر هذا السكربت خمسة مدخلات خاطئة إلى number_to_words():

<?php
require __DIR__.'/vendor/autoload.php';

use function RtlyKit\number_to_words;
use RtlyKit\Exceptions\RtlyKitThrowable;

foreach ([1.5, NAN, 'abc', str_repeat('9', 22), str_repeat('1', 5000)] as $input) {
    try {
        number_to_words($input);
    } catch (RtlyKitThrowable $e) {
        echo (new ReflectionClass($e))->getShortName(), ' ',
            $e->getErrorCode()->value, ' ',
            json_encode($e->getContext()), "\n";
    }
}
// InvalidNumberException invalid_number []
// InvalidNumberException non_finite_number []
// InvalidNumberException invalid_number []
// InvalidNumberException number_too_large {"limit":"10^21 - 1"}
// InvalidNumberException input_too_long {"limit":4096}

أنماط الالتقاط#

تفرّع بحسب الرمز لا بحسب الرسالة. استخدم match مع فرع default، لأن حالات جديدة قد تُضاف في إصدار فرعي.

<?php
require __DIR__.'/vendor/autoload.php';

use RtlyKit\Calendar\Jalali;
use RtlyKit\Exceptions\{ErrorCode, InvalidDateException, RtlyKitThrowable};

function category(RtlyKitThrowable $e): string
{
    return match ($e->getErrorCode()) {
        ErrorCode::InvalidDate, ErrorCode::DateOutOfRange => 'date',
        ErrorCode::InputTooLong => 'too-long',
        default => 'other',
    };
}

try {
    Jalali::create(1404, 13, 1);          // لا يوجد شهر 13
} catch (RtlyKitThrowable $e) {
    echo category($e), "\n";               // date
}

$e = InvalidDateException::because(ErrorCode::DateOutOfRange, 'Year is out of range', ['year' => 99999]);
echo $e->getErrorCode()->value, ' ', json_encode($e->getContext()), "\n";
// date_out_of_range {"year":99999}

try {
    Jalali::create(1404, 13, 1);
} catch (\InvalidArgumentException $e) {   // الالتقاط بالأسلوب القديم ما زال يعمل
    echo get_class($e), ' ', $e instanceof RtlyKitThrowable ? 'is RtlyKitThrowable' : '', "\n";
    // RtlyKit\Exceptions\InvalidDateException is RtlyKitThrowable
}

RtlyKitException::because(ErrorCode, string $message, array $context = [], ?Throwable $previous = null) هي الدالة المنشئة المسمّاة التي تستخدمها المكتبة نفسها لرمي استثناء برمز محدد. يمكنك استخدامها لإخفاقاتك أنت أيضاً، لكن الرمز الذي تضيفه بهذه الطريقة مسؤوليتك أنت وليس جزءًا من عقد المكتبة.

عند التسجيل في السجل، اكتب الرمز والسياق بدلًا من نص الرسالة:

catch (RtlyKitThrowable $e) {
    error_log($e->getErrorCode()->value.' '.json_encode($e->getContext()));
}

ما لا يرمي أبدًا#

تقبل أدوات التحقق أي قيمة PHP. ولا ترفع استثناءً بسبب مدخل خاطئ مهما كان نوعه أو حجمه:

<?php
require __DIR__.'/vendor/autoload.php';

use function RtlyKit\validate_national_code;
use RtlyKit\Validation\Sheba;

var_dump(Sheba::isValid(null));                                  // bool(false)
echo json_encode(validate_national_code([])->errors()), "\n";                    // ["invalid_type"]
echo json_encode(validate_national_code(str_repeat('1', 5000))->errors()), "\n"; // ["input_too_long"]
echo json_encode(validate_national_code('0013542418')->errors()), "\n";          // ["invalid_checksum"]
var_dump(validate_national_code('0013542419')->isValid());       // bool(true)
مدخل أداة التحققالنتيجة
string حتى 4096 بايتيُفحص بصورة طبيعية (الأرقام الفارسية والعربية مقبولة)
int، وfloat منتهٍ وصحيح القيمةيُحوَّل إلى نص ثم يُفحص (الأصفار البادئة مفقودة أصلًا في int)
null، وbool، والمصفوفات، والكائنات، وNaN، وINF، والأعداد الكسريةغير صالح، والخطأ invalid_type
string أكبر من 4096 بايتغير صالح، والخطأ input_too_long
UTF-8 غير صالحغير صالح (عادةً invalid_format)

تتبع قواعد التحقق في Laravel والتفعيل الاختياري Globals::register() القاعدة نفسها: تُرجع القواعد قيمة منطقية، ولا ترمي Globals::register() استثناءً أبدًا (بل تُرجع الأسماء التي تخطّتها).

سقوف المدخلات#

لكل استدعاء يأخذ نصًا غير موثوق سقف للحجم، فلا يكلّف مدخل خبيث الكثير. تجاوز السقف يرفع input_too_long، إما كاستثناء أو كخطأ من أداة التحقق. القائمة الكاملة في الحدود.

الموضعالسقفعند التجاوز
أدوات التحقق4096 بايت لكل نصResult غير صالح، والخطأ input_too_long
NumberToWords::convert() / fromWords()، ومدخل Format النصي4096 بايت لكل نصInvalidNumberException
Format::withSeparator() / format_number()1000 نويسة في العدد العشري العاديInvalidNumberException
فاصل Slugify::make()64 بايت، UTF-8 صالحRtlyKitException
نمط format() (Jalali وHijri وHebrew)256 بايت (MAX_FORMAT_LENGTH في كل فئة)InvalidDateException

قاعدة نطاق التقويم#

كل نقطة دخول في التقويم (make وcreate وcreateFromFormat والطوابع الزمنية وadd* / sub* والدوال المساعدة وماكروات Carbon) إما تُرجع تاريخًا صالحًا وإما ترمي InvalidDateException. لا شيء يلتف حول النطاق ولا يُستخدم أي نوع استثناء آخر. النطاقات: الجلالي من -620 إلى 9377، والهجري من 1 إلى 9665، والعبري من 3762 إلى 13759، وكلها ضمن السنوات الميلادية من 1 إلى 9999. راجع الحدود.

مثال كامل#

use function RtlyKit\jdate;
use function RtlyKit\number_to_words;
use RtlyKit\Exceptions\InvalidDateException;
use RtlyKit\Exceptions\RtlyKitThrowable;

try {
    $date  = jdate($userInput);
    $words = number_to_words($userAmount);
} catch (InvalidDateException $e) {
    echo "Please enter a valid date.\n";
} catch (RtlyKitThrowable $e) {
    error_log($e->getErrorCode()->value.' '.json_encode($e->getContext()));
}