On this page
Two failure styles#
Each function in RTLY-Kit uses one of two styles, so you always know what to guard:
- Validators never throw.
NationalCode,Sheba,BankCard,Mobile,PostalCodeandVehiclePlate(and theis_*andvalidate_*helpers) accept any value and answer withfalseor an invalidResult. See Validators overview. - Everything else throws a
RtlyKit\Exceptions\RtlyKitException(or a subclass) for input it cannot accept. ATypeError,ValueErroror PHPDateMalformed*exception never escapes for bad input. If you see one, it is a library bug, and we would like a report.
Exception hierarchy#
\InvalidArgumentException
└─ RtlyKit\Exceptions\RtlyKitException implements RtlyKitThrowable
├─ InvalidDateException
├─ InvalidNumberException
├─ InvalidPrayerConfigException
└─ UnsupportedLocaleException
RtlyKitThrowable is a marker interface. It declares getErrorCode(): ErrorCode and getContext(): array. Catch it, or RtlyKitException, to handle the whole library with one clause. RtlyKitException extends \InvalidArgumentException, so existing catch (\InvalidArgumentException) and catch (\LogicException) code keeps working.
| Exception | Thrown for |
|---|---|
InvalidDateException | impossible dates, years outside the supported range, blank or unreadable strings, huge add*() and sub*() amounts, out-of-range timestamps, over-long format patterns, bad holiday dates and settings |
InvalidNumberException | non-numeric or fractional input, NaN and INF, oversized input, numbers that are too large, unknown number words, bad Arabic number options |
InvalidPrayerConfigException | unknown prayer method, unknown city, invalid Asr factor, invalid tune values |
UnsupportedLocaleException | a locale the function does not support (for example number_to_words(5, 'de')) |
RtlyKitException itself | other bad arguments, such as an invalid or over-long Slugify::make() separator or invalid UTF-8 text, or a missing bundled data table |
Error codes#
Exception messages are for people and may be reworded in any release. The error code is the stable part. It is an ErrorCode backed string enum, and its case names and values are public API (see API stability).
| Case | Value | Meaning |
|---|---|---|
InvalidArgument | invalid_argument | fallback for an invalid argument with no more specific code, such as a wrong option for Arabic number words |
InvalidDate | invalid_date | malformed, impossible or unreadable date or time (month 13, 30 Esfand in a non-leap year, 'not a date', a blank string) |
DateOutOfRange | date_out_of_range | a year, date, timestamp or add*() and sub*() amount outside the supported range (see Limits) |
InvalidNumber | invalid_number | the value cannot be read as a whole number |
NumberTooLarge | number_too_large | a number larger than the supported size |
NonFiniteNumber | non_finite_number | NaN or INF where a finite number is needed |
InvalidNumberWords | invalid_number_words | unknown or wrongly ordered number words in NumberToWords::fromWords() |
InputTooLong | input_too_long | string input over a documented size cap |
InvalidPrayerConfig | invalid_prayer_config | unknown city, calculation method or Asr factor, or an invalid tune |
UnsupportedLocale | unsupported_locale | a locale that is not supported |
DataUnavailable | data_unavailable | a bundled data table is missing or damaged (a packaging problem, not a user error) |
Result::errors() (invalid_format, invalid_checksum, invalid_type and so on) are plain strings from the validators, not ErrorCode cases. The enum is for exceptions only.Reading the code and the context#
Every library exception has getErrorCode() and getContext(). The context is an array of facts about the failure that a program can read (a limit, the locale, the name of the argument). It never contains secrets and may be empty. This script feeds five bad inputs to 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}
Catching patterns#
Branch on the code, not on the message. Use match with a default arm, because a minor release may add new cases.
<?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); // there is no month 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) { // old-style catch still works
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) is the named constructor the library uses to raise an exception with a specific code. You can use it for your own failures too. The code you add this way is your own and not part of the contract of the library.
When you log, write the code and the context and not the message text:
catch (RtlyKitThrowable $e) {
error_log($e->getErrorCode()->value.' '.json_encode($e->getContext()));
}
What never throws#
The validators accept any PHP value. They raise no exception for bad input, whatever its type or size:
<?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)
| Validator input | Result |
|---|---|
string up to 4096 bytes | checked as usual (Persian and Arabic digits are accepted) |
int, whole finite float | turned into a string, then checked (an int has already lost its leading zeros) |
null, bool, arrays, objects, NaN, INF, fractional floats | invalid, error invalid_type |
string over 4096 bytes | invalid, error input_too_long |
| invalid UTF-8 | invalid (usually invalid_format) |
The Laravel validation rules and the Globals::register() opt-in follow the same rule. The rules return a boolean, and Globals::register() never throws. It returns the names it skipped.
Input caps#
Every call that takes untrusted text has a size cap, so a hostile input cannot cost much. Going over a cap raises input_too_long, as an exception or as a validator error. The full list is on Limits.
| Where | Cap | When exceeded |
|---|---|---|
| Validators | 4096 bytes per string | invalid Result, error input_too_long |
NumberToWords::convert() and fromWords(), Format string input | 4096 bytes per string | InvalidNumberException |
Format::withSeparator() and format_number() | 1000 characters in the plain decimal number | InvalidNumberException |
Slugify::make() separator | 64 bytes, valid UTF-8 | RtlyKitException |
format() pattern (Jalali, Hijri, Hebrew) | 256 bytes (MAX_FORMAT_LENGTH on each class) | InvalidDateException |
The calendar range rule#
Every calendar entry point (make, create, createFromFormat, timestamps, add* and sub*, the helpers and the Carbon macros) either returns a valid date or throws InvalidDateException. Nothing wraps around, and no other exception type is used. The ranges are Jalali -620 to 9377, Hijri 1 to 9665 and Hebrew 3762 to 13759, all within Gregorian years 1 to 9999. See Limits.
A complete example#
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()));
}