All guides
No guide matches your search.
Validators overview
How the six Iranian validators work. The shared Validator contract, the Result object, stable error keys, input cleaning and size limits.
On this page
The six validators
RTLY-Kit has six validators for data that Iranian apps handle every day. They all work the same way. Once you know one, you know them all.
| Class | Checks | Page |
|---|---|---|
NationalCode | 10 digits, mod-11 check digit, place-of-issue hint | National code |
Sheba | IR + 24 digits, ISO 7064 mod-97-10, bank lookup | Sheba and bank card |
BankCard | 16 digits, Luhn checksum, bank lookup by BIN | Sheba and bank card |
Mobile | Shape of 09xxxxxxxxx in five input forms, operator hint, allocation flag | Mobile, postal code, plate |
PostalCode | 10 digits, no 0 or 2 in the first five | Mobile, postal code, plate |
VehiclePlate | Passenger-car plate shape, split into parts | Mobile, postal code, plate |
All of them live in the RtlyKit\Validation namespace. They accept Persian and Arabic digits as users type them, so you can pass form input straight in.
Two ways to call
Every validator gives you a plain boolean and a structured result. Use isValid() when you only need yes or no. Use validate() when you want to tell the user why a value was rejected.
<?php
require 'vendor/autoload.php';
use RtlyKit\Validation\NationalCode;
use function RtlyKit\is_national_code;
var_dump(NationalCode::isValid('۰۴۹۹۳۷۰۸۹۹')); // bool(true)
var_dump(is_national_code('0499370899')); // bool(true)
$result = NationalCode::validate('0499370898');
var_dump($result->isValid()); // bool(false)
print_r($result->errors()); // Array ( [0] => invalid_checksum )The helpers (is_national_code(), validate_sheba() and the others) are namespaced. Import them with use function RtlyKit\is_national_code;. Short global names are opt-in. See Helpers and globals.
The Validator contract
Every validator implements RtlyKit\Contracts\Validator. It has two static methods:
validate(mixed $value): ResultisValid(mixed $value): bool, a shortcut forvalidate($value)->isValid()
Validators keep no state, so the methods are static. That lets you pick a validator at run time from a class name, for example from a config array:
use RtlyKit\Contracts\Validator;
use RtlyKit\Validation\Mobile;
use RtlyKit\Validation\NationalCode;
use RtlyKit\Validation\PostalCode;
/** @var array<string, class-string<Validator>> $rules */
$rules = [
'national_code' => NationalCode::class,
'mobile' => Mobile::class,
'postal' => PostalCode::class,
];
$input = ['national_code' => '0499370899', 'mobile' => '0912', 'postal' => '1593715416'];
foreach ($rules as $field => $class) {
$result = $class::validate($input[$field]);
echo $field, ': ', $result->isValid() ? 'ok' : implode(',', $result->errors()), "\n";
}
// national_code: ok
// mobile: invalid_format
// postal: okNever throws. Neither method throws for bad input. Any value, including null, arrays, objects, oversized strings and invalid UTF-8, gives an invalid Result with a stable error key. Exceptions are for programmer errors elsewhere in the library. See Error handling.
The Result object
RtlyKit\Validation\Result is an immutable value object with three read methods:
| Method | Returns |
|---|---|
isValid() | bool |
errors() | list<string>: stable error keys, empty when valid |
details() | array<string, mixed>: facts the validator found while checking |
Details are present on valid and invalid results, as far as the validator got before it stopped. So you can show the cleaned value or a bank name even next to an error:
use RtlyKit\Validation\BankCard;
$result = BankCard::validate('6037991234567890');
var_dump($result->isValid()); // bool(false)
print_r($result->errors()); // Array ( [0] => invalid_checksum )
echo json_encode($result->details(), JSON_UNESCAPED_UNICODE);
// {"normalized":"6037991234567890","bin":"603799","bank_name":"بانک ملی ایران"}Detail keys per validator
| Validator | Detail keys |
|---|---|
NationalCode | normalized, location ({province, city} or null) |
Sheba | normalized, bank_code, bank_name |
BankCard | normalized, bin, bank_name |
Mobile | normalized, operator, allocated |
PostalCode | normalized |
VehiclePlate | normalized, and when the shape matched: two_digit, letter, three_digit, region |
Lookup values (location, bank_name, operator) are null when the table has no entry. A missing entry means "unknown", never "invalid".
Error keys
Errors are stable keys that a program can read. They do not change between releases, so you can map them to your own messages or translations. The Laravel integration does this. See Laravel validation and cast.
| Key | Meaning | Used by |
|---|---|---|
invalid_type | The value is not a string, an int or an integral finite float | all |
input_too_long | A string longer than 4096 bytes | all |
invalid_format | The shape is wrong (characters, digit count, pattern) | all except PostalCode, which uses it for a 0 or 2 in the first five digits |
invalid_length | The digit count is not 10 | PostalCode |
repeated_digits | Every digit is the same (1111111111) | NationalCode, BankCard |
invalid_checksum | The check digit or digits do not match | NationalCode, Sheba, BankCard |
invalid_region | A numeric part of the plate is all zeros | VehiclePlate |
A result carries at most one error today, the first check that failed. errors() returns a list so that later versions can report several.
Input handling
Before any rule runs, the value goes through one shared gate:
- Strings up to 4096 bytes pass. Persian and Arabic digits take two bytes each, so the cap is about 2000 such digits. A longer string gives
input_too_long. intis converted with(string).floatmust be finite, whole, and below 10 15 in size. It is converted without an exponent.NaN,INF,1.5and huge floats giveinvalid_type.- Everything else (
null,bool, arrays, objects) givesinvalid_type.
use RtlyKit\Validation\NationalCode;
use RtlyKit\Validation\PostalCode;
print_r(NationalCode::validate(null)->errors()); // [invalid_type]
print_r(NationalCode::validate(1.5)->errors()); // [invalid_type]
print_r(NationalCode::validate(str_repeat('1', 4097))->errors()); // [input_too_long]
print_r(NationalCode::validate("\xff\xfe")->errors()); // [invalid_format]
var_dump(PostalCode::validate(1593715416)->isValid()); // bool(true)Good to know. A national code or postal code passed as an integer loses its leading zeros. 499370899 is not 0499370899, and the validator only sees nine digits. Keep identifiers as strings from the form or database column all the way to the validator.
Cleaning the input
After the gate, each validator cleans the string with its own public normalize() method. They share these rules:
- Persian (
۰-۹) and Arabic-Indic (٠-٩) digits become English digits, usingDigits::toEnglish(). - Separators that users type are removed: spaces, non-breaking spaces, the half-space (ZWNJ), the LRM and RLM marks and hyphens.
Mobile,BankCardandPostalCodealso allow parentheses and dots, and then keep only the digits. Letters, other punctuation, a leading hyphen (a negative number) and control characters such as a tab, newline or NUL are never removed: they giveinvalid_format. Shebaalso upper-cases the text and adds theIRprefix to a bare 24-digit string.
The cleaned value is always in details()['normalized']. Store that, not the raw input, so the same number is never saved in two spellings.
Lookup tables and where they come from
Four validators attach a lookup: bank by card BIN, bank by Sheba code, place of issue by national-code prefix, and operator by mobile prefix. The lookups are extras on top of validation. Validity never depends on them.
- Bank card BINs: 39 entries. Sheba bank codes: 38 entries. National-code prefixes: 547 entries.
- An entry is included only when at least two independent sources agree. A missing entry means unknown.
- The 19 Sheba codes in the Central Bank's published specification all match our table, and every mobile prefix we list lies inside a block that the national numbering plan names for mobile service.
Good to know. The lookup tables come from public lists. They are not an official registry. Banks merge and rename, and operators are portable. Treat a lookup result as a hint. Do not base a legal, financial or identity decision on it. Sources and dates are in Accuracy and data.
Where to go next
- National code : the algorithm, cleaning and the place-of-issue hint.
- Sheba and bank card : mod-97 and Luhn, bank lookups.
- Mobile, postal code and vehicle plate .
- Limits : every size cap in one place.
Was this page helpful?