All guides
No guide matches your search.
Validation rules and Eloquent cast
Use the Iranian validation rules in Laravel forms, store Jalali dates in Eloquent models with JalaliCast, and show them in Blade.
On this page
Validation rules
Once the package is installed (Laravel setup), six rules are available in any validator, form request or $request->validate() call. They have seven names, because mobile is a short alias of iran_mobile. They wrap the validators described on the validation pages.
| Rule | Accepts | Details |
|---|---|---|
national_code | Iranian national code | National code |
sheba | Sheba / IBAN, with or without the IR prefix | Sheba and bank card |
bank_card | 16-digit bank card number (Luhn check) | Sheba and bank card |
iran_mobile | Iranian mobile number | Mobile, postal code, plate |
mobile | Short alias of iran_mobile | same behaviour |
postal_code | 10-digit Iranian postal code | Mobile, postal code, plate |
vehicle_plate | Iranian vehicle plate, for example 12ب345-67 | Mobile, postal code, plate |
Good to know. mobile is a generic word. If another package or your own code registers a rule named mobile, use iran_mobile everywhere so there is no doubt which rule runs.
Using them
use Illuminate\Http\Request;
public function store(Request $request)
{
$data = $request->validate([
'national_code' => 'required|national_code',
'mobile' => 'required|iran_mobile',
'card' => 'nullable|bank_card',
'iban' => 'nullable|sheba',
'postal' => 'required|postal_code',
'plate' => 'nullable|vehicle_plate',
]);
}A real validation run with bad data and the locale fa gave:
// national_code = '1234567890', mobile = '0912', iban = 'IR000000000000000000000000'
// card, postal and plate hold valid values and pass.
//
// national_code: national code کد ملی معتبر نیست.
// mobile: mobile شماره موبایل معتبر نیست.
// iban: iban شماره شبا معتبر نیست.The English text is "The national code is not a valid Iranian national code." and an Arabic text is available too. See translations and overriding messages.
What input the rules accept
- Strings. Persian and Arabic digits are accepted.
۰۰۱۳۵۴۲۴۱۹passesnational_code. - Integers and floats reach the rule unchanged. The validator turns them into a string without scientific notation. Be careful with numbers that start with zero. The JSON number
13542419has lost the leading zeros of0013542419and fails. Send national codes, mobile numbers and postal codes as strings. - Arrays, objects and booleans never reach the validator and fail the rule.
- Empty values. As with Laravel's own rules, an empty string and a missing key are skipped unless the field is
required. An explicitnullfails unless the field isnullable. Say what you mean withrequiredornullable.
$f->make(['n' => '۰۰۱۳۵۴۲۴۱۹'], ['n' => 'national_code'])->passes(); // true
$f->make(['n' => 13542419], ['n' => 'national_code'])->passes(); // false (leading zeros lost)
$f->make(['n' => null], ['n' => 'nullable|national_code'])->passes(); // true
$f->make(['n' => null], ['n' => 'national_code'])->passes(); // false
$f->make(['n' => ''], ['n' => 'national_code'])->passes(); // true (skipped)
$f->make(['n' => ''], ['n' => 'required|national_code'])->passes(); // false
$f->make([], ['n' => 'national_code'])->passes(); // true (key missing)If you need the reason a value failed (wrong length, bad checksum, wrong type), call the standalone validators. They return a structured Result. See Validators overview.
The JalaliCast Eloquent cast
RtlyKit\Laravel\Casts\JalaliCast keeps the database column in Gregorian (Y-m-d H:i:s), as every tool expects. On the model, the attribute is an immutable RtlyKit\Calendar\Jalali object.
use Illuminate\Database\Eloquent\Model;
use RtlyKit\Laravel\Casts\JalaliCast;
class Post extends Model
{
protected $guarded = [];
protected $casts = [
'published_at' => JalaliCast::class,
];
}
$post = new Post();
$post->published_at = '1404/01/15 10:30';
echo $post->getAttributes()['published_at']; // 2025-04-04 10:30:00 (stored value)
echo $post->published_at->format('Y/m/d H:i'); // 1404/01/15 10:30
echo get_class($post->published_at); // RtlyKit\Calendar\Jalali
echo $post->published_at->addDays(20)->format('Y/m/d'); // 1404/02/04The column type stays datetime or timestamp. You need no migration change.
Accepted values on assignment
| Value assigned | Stored (Gregorian) |
|---|---|
Jalali string '1404/01/15 10:30' | 2025-04-04 10:30:00 |
Jalali date only, Persian digits and dashes '۱۴۰۴-۰۱-۱۵' | 2025-04-04 00:00:00 |
Gregorian string '2025-04-04 08:00:00' | 2025-04-04 08:00:00 |
Unix timestamp (int) 1743753600 | 2025-04-04 08:00:00 (with a UTC default time zone) |
Any DateTimeInterface (Carbon included) | the same moment, as Y-m-d H:i:s |
A Jalali object | its Gregorian equivalent |
null or '' | null |
A string is read as Jalali when its year is 1200 to 1599 and the separator is / or -, with an optional time HH:MM or HH:MM:SS after a space or T. Any other string goes to Jalali::make(). It reads a year below 1700 as Jalali and anything else as Gregorian, so '1650-01-01' is still Jalali.
When the model reads the column, the stored value is always read as Gregorian, whatever the year, so a date such as 1650-05-05 10:00:00 comes back unchanged. A stored value of an unsupported type (an array, a boolean, a float or an object) throws InvalidDateException with the attribute name and the type. Only null and an empty string give null.
Errors
Bad input throws RtlyKit\Exceptions\InvalidDateException when you assign it, never a raw PHP error:
$post->published_at = '1404/13/01'; // Invalid Jalali date: 1404/13/1
$post->published_at = '1404/07/31'; // Invalid Jalali date: 1404/7/31 (Mehr has 30 days)
$post->published_at = 'not a date'; // Unable to parse date: not a date
$post->published_at = 1.5; // Cannot cast value for 'published_at' to a date.
$post->published_at = ['x']; // Cannot cast value for 'published_at' to a date.Validate user input before it reaches the model (see the form request below), so a typo gives a form error and not an exception. Reading is relaxed for null and the empty string (both give null). An attribute that holds unreadable text in the database throws InvalidDateException when you read it.
Serialising a model
Jalali, Hijri and Hebrew implement JsonSerializable. toJson() on a model with a JalaliCast attribute gives the same text as (string) $date, for example {"published_at":"1404/01/01 10:00:00"}. toArray() keeps the Jalali object. Format it yourself if you need another pattern.
Form requests
There is no built-in rule for a Jalali date field. Check the shape with Laravel's regex rule or a small custom rule, then let the cast do the conversion:
public function rules(): array
{
return [
'published_at' => ['required', 'regex:/^1[2-5]\d\d[\/-]\d{1,2}[\/-]\d{1,2}$/u'],
];
}A well-formed but impossible date such as 1404/07/31 still reaches the cast and throws there. Catch InvalidDateException in a custom rule or in the controller if users type free text.
Blade
The cast gives you a Jalali object, so Blade needs no helper:
{{ $post->published_at?->format('Y/m/d') }} {{-- 1404/01/15 --}}
{{ $post->published_at?->format('l j F Y') }} {{-- جمعه 15 فروردین 1404 --}}
{{ \RtlyKit\to_persian_digits($post->published_at->format('Y/m/d')) }} {{-- ۱۴۰۴/۰۱/۱۵ --}}For a column that is not cast, such as the standard created_at (a Carbon instance), use the namespaced helper. Namespaced helpers are always loaded, so you register nothing:
{{ \RtlyKit\jdate($post->created_at)->format('Y/m/d H:i') }} {{-- 1404/01/15 08:00 --}}If you use it often, import the function at the top of a Blade file with @php use function RtlyKit\jdate; @endphp. With the Carbon macros the same call is $post->created_at->jformat('Y/m/d'). See Carbon macros.
Note. A null attribute is not formatted for you. Use the nullsafe operator (?->) as shown, or Blade throws on null. For the full list of format letters, see the Jalali page.
Was this page helpful?