مدیریت خطا

کدام فراخوانی‌ها خطا پرتاب می‌کنند و کدام نه، سلسله‌مراتب خطاها، مقدارهای پایدار ErrorCode و روش درست گرفتن و ثبت خطا.

در این صفحه
  1. دو سبک برای شکست
  2. سلسله‌مراتب خطاها
  3. کدهای خطا
  4. خواندن کد و context
  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 یک اینترفیس نشانه است که getErrorCode(): ErrorCode و getContext(): array را دارد. اگر آن را (یا RtlyKitException را) بگیرید، با یک catch کل کتابخانه را پوشش داده‌اید. چون RtlyKitException از \InvalidArgumentException ارث می‌برد، کدهای قدیمی که catch (\InvalidArgumentException) یا catch (\LogicException) دارند هنوز کار می‌کنند.

خطاچه وقت پرتاب می‌شود
InvalidDateExceptionتاریخ غیرممکن، سال بیرون از بازه، رشتهٔ خالی یا غیرقابل خواندن، مقدار غیرمنطقی در add*() و sub*()، زمان یونیکس بیرون از بازه، الگوی قالب‌بندی خیلی بلند، گزینهٔ نادرست در HolidayCalendar
InvalidNumberExceptionورودی غیرعددی یا اعشاری، NaN و INF، ورودی یا عدد خیلی بزرگ، واژهٔ عددی ناشناخته، گزینهٔ نادرست برای عدد به حروف عربی
InvalidPrayerConfigExceptionروش محاسبه یا شهر ناشناخته، ضریب عصر نامعتبر، تنظیم دستی نادرست در withTune()
UnsupportedLocaleExceptionزبانی که تابع پشتیبانی نمی‌کند (مثلاً number_to_words(5, 'de'))
خود RtlyKitExceptionآرگومان‌های نامعتبر دیگر، مثل جداکنندهٔ بد یا متن UTF-8 خراب در Slugify::make() یا نبودن جدول داده

کدهای خطا#

متن پیام برای آدم‌هاست و ممکن است در هر نسخه عوض شود. بخش پایدار کد خطا است: یک enum رشته‌ای به نام ErrorCode. نام caseها و مقدارهایش جزو API عمومی است (پایداری API).

Caseمقدارمعنا
InvalidArgumentinvalid_argumentآرگومان نامعتبری که کد دقیق‌تری ندارد
InvalidDateinvalid_dateتاریخ یا زمان نادرست، غیرممکن یا غیرقابل خواندن (ماه ۱۳، ۳۰ اسفند در سال غیرکبیسه، 'not a date'، رشتهٔ خالی)
DateOutOfRangedate_out_of_rangeسال، تاریخ، زمان یونیکس یا مقدار add*() و sub*() بیرون از بازه (سقف‌ها)
InvalidNumberinvalid_numberمقدار را نمی‌شود عدد (صحیح) خواند
NumberTooLargenumber_too_largeعدد از بیشترین مقدار پشتیبانی‌شده بزرگ‌تر است
NonFiniteNumbernon_finite_numberجایی که عدد متناهی لازم است، NaN یا INF داده شده
InvalidNumberWordsinvalid_number_wordsواژهٔ عددی ناشناخته یا نادرست در NumberToWords::fromWords()
InputTooLonginput_too_longورودی رشته‌ای از سقف مشخص‌شده بلندتر است
InvalidPrayerConfiginvalid_prayer_configشهر، روش محاسبه، ضریب عصر یا تنظیم دستی نادرست
UnsupportedLocaleunsupported_localeزبانی که پشتیبانی نمی‌شود
DataUnavailabledata_unavailableیک جدول داده در بسته نیست یا خراب است (مشکل بسته‌بندی است، نه اشتباه کاربر)
یادآوری. رشته‌هایی که Result::errors() می‌دهد (invalid_format، invalid_checksum، invalid_type و ...) رشته‌های ساده و مخصوص اعتبارسنج‌ها هستند، نه caseهای ErrorCode. این enum فقط برای خطاهای پرتاب‌شده است.

خواندن کد و context#

هر خطای کتابخانه getErrorCode() و getContext() دارد. context آرایه‌ای از اطلاعات ماشین‌خوان دربارهٔ شکست است (یک سقف، زبان پشتیبانی‌نشده، نام آرگومان). چیز محرمانه‌ای در آن نیست و ممکن است خالی باشد. این اسکریپت پنج ورودی بد را به 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 استفاده کنید، چون ممکن است در نسخهٔ فرعی caseهای تازه اضافه شود.

<?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);          // ماه ۱۳ وجود ندارد
} 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) {   // catch قدیمی هنوز کار می‌کند
    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) سازندهٔ خود کتابخانه برای ساختن خطا با کد مشخص است. می‌توانید برای خطاهای خودتان هم به کار ببرید، ولی خطاهای ساخته‌شده توسط شما جزو تعهد کتابخانه نیست.

در لاگ، کد و context را بنویسید، نه متن پیام را:

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)
ورودی اعتبارسنجنتیجه
رشته تا ۴۰۹۶ بایتطبق معمول بررسی می‌شود (رقم فارسی و عربی قبول است)
int و float صحیح و متناهیبه رشته تبدیل و بعد بررسی می‌شود (صفرهای اول int از قبل از بین رفته‌اند)
null، bool، آرایه، شیء، NaN، INF، float اعشارینامعتبر، با invalid_type
رشتهٔ بیشتر از ۴۰۹۶ بایتنامعتبر، با input_too_long
UTF-8 خرابنامعتبر (معمولاً invalid_format)

قانون‌های اعتبارسنجی Laravel و Globals::register() هم همین اصل را دارند. قانون‌ها یک مقدار بولی می‌دهند و Globals::register() هیچ‌وقت خطا پرتاب نمی‌کند (نام‌های ردشده را برمی‌گرداند).

سقف ورودی‌ها#

هر جا متن نامطمئن بیاید سقف اندازه دارد، تا ورودی مخرب هزینهٔ زیادی نسازد. عبور از سقف input_too_long می‌دهد، به شکل خطای پرتاب‌شده یا خطای اعتبارسنج. فهرست کامل در سقف‌ها است.

کجاسقفاگر ردش کنید
اعتبارسنج‌ها۴۰۹۶ بایت برای هر رشتهResult نامعتبر با input_too_long
ورودی رشته‌ای NumberToWords::convert() و fromWords() و Format۴۰۹۶ بایت برای هر رشتهInvalidNumberException
Format::withSeparator() و format_number()۱۰۰۰ نویسه در عدد اعشاری سادهInvalidNumberException
جداکنندهٔ Slugify::make()۶۴ بایت، UTF-8 معتبرRtlyKitException
الگوی format() (جلالی، هجری، عبری)۲۵۶ بایت (MAX_FORMAT_LENGTH در هر کلاس)InvalidDateException

قاعدهٔ بازهٔ تقویم#

هر راه ورود به تقویم (make، create، createFromFormat، زمان یونیکس، add* و sub*، توابع کمکی و ماکروهای Carbon) یا تاریخ معتبر می‌دهد یا InvalidDateException. چیزی سرریز نمی‌کند و خطای از نوع دیگری هم نمی‌آید. بازه‌ها: جلالی ‎-۶۲۰ تا ۹۳۷۷، هجری ۱ تا ۹۶۶۵ و عبری ۳۷۶۲ تا ۱۳۷۵۹، که همه در سال‌های میلادی ۱ تا ۹۹۹۹ جا می‌شوند. سقف‌ها را ببینید.

یک نمونهٔ کامل#

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 "لطفاً تاریخ معتبر وارد کنید.\n";
} catch (RtlyKitThrowable $e) {
    error_log($e->getErrorCode()->value.' '.json_encode($e->getContext()));
}