alihoushy / iranian-national-id
Validate Iranian national ID (کد ملی) with the official mod-11 checksum and resolve the issuing province and city.
Requires
- php: ^8.1
- ext-mbstring: *
Requires (Dev)
- laravel/pint: ^1.18
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^10.5 || ^11.0
README
کد ملی ایران
اعتبارسنجی کد ملی ایران با الگوریتم رسمی mod-11 و استخراج استان و شهر محل صدور از سه رقم نخست.
این پکیج PHP خالص است؛ به هیچ فریمورکی وابسته نیست و در Laravel، Symfony، Slim یا پروژههای بدون فریمورک به یک شکل کار میکند.
چرا این پکیج؟
- بدون وابستگی: هیچ پکیج دیگری نصب نمیکند و به اکستنشن
intlنیاز ندارد. - ورودی واقعی را میپذیرد: ارقام فارسی و عربی-هندی، خط تیره، فاصله، نیمفاصله و کدهای ۸ و ۹ رقمیِ بدون صفر ابتدایی.
- صادق درباره ابهام: برای ۱۳ کد، منبع بیش از یک محل صدور ثبت کرده است. این پکیج بهجای حدس زدن، ابهام را صریح گزارش میکند.
- تغییرناپذیر: هر نمونه
NationalIdتضمینشده معتبر است؛ اعتبارسنجی فقط یکبار در لحظه ساخت انجام میشود.
نیازمندیها
- PHP نسخه ۸.۱ یا بالاتر
- اکستنشن
mbstring
نصب
composer require alihoushy/iranian-national-id
شروع سریع
<?php use AliHoushy\IranianNationalId\NationalId; // سادهترین حالت: فقط میخواهیم بدانیم معتبر است یا نه NationalId::isValid('0499370899'); // true NationalId::isValid('0499370898'); // false — رقم کنترل نادرست NationalId::isValid('1111111111'); // false — ارقام تکراری // ساخت value object؛ ورودی نامعتبر استثنا پرتاب میکند $id = NationalId::from('۰۴۹-۹۳۷۰۸۹-۹'); $id->value(); // '0499370899' — همیشه ۱۰ رقم لاتین $id->format(); // '049-937089-9' $id->checkDigit(); // 9 $id->issuanceCode(); // '049' // محل صدور شناسنامه $place = $id->issuancePlace(); $place->city; // 'شهرری' $place->province; // 'تهران' (string) $place; // 'شهرری، تهران'
نرمالسازی ورودی
ورودیهای زیر همگی به کد ملی یکسان 0499370899 تبدیل میشوند:
NationalId::from('0499370899'); // ارقام لاتین NationalId::from('۰۴۹۹۳۷۰۸۹۹'); // ارقام فارسی NationalId::from('٠٤٩٩٣٧٠٨٩٩'); // ارقام عربی-هندی NationalId::from('049-937089-9'); // با خط تیره NationalId::from(' 049 937 089 9 '); // با فاصله NationalId::from('499370899'); // ۹ رقمی، با صفر پر میشود
کدهای ۸ و ۹ رقمی از سمت چپ با صفر به ۱۰ رقم میرسند، چون در بسیاری از سامانهها صفرهای ابتدایی حذف میشوند. ورودی کوتاهتر از ۸ رقم پذیرفته نمیشود.
اگر فقط به نرمالسازی نیاز دارید و نمیخواهید رقم کنترل سنجیده شود:
NationalId::normalize('۴۹۹-۳۷۰۸۹۹'); // '0499370899' NationalId::normalize('سلام'); // null
مدیریت خطا
سه راه برای برخورد با ورودی نامعتبر وجود دارد؛ هرکدام را که با سبک کدتان جور است انتخاب کنید:
use AliHoushy\IranianNationalId\Exception\InvalidNationalIdException; // ۱) بولین ساده if (! NationalId::isValid($input)) { /* ... */ } // ۲) خروجی nullable $id = NationalId::tryFrom($input); if ($id === null) { /* ... */ } // ۳) استثنا با پیام فارسی و دسترسی به ورودی خام try { $id = NationalId::from($input); } catch (InvalidNationalIdException $e) { $e->getMessage(); // 'رقم کنترل کد ملی با الگوریتم رسمی mod-11 همخوانی ندارد.' $e->input(); // ورودی خام کاربر، پیش از نرمالسازی }
همه استثناهای پکیج رابط AliHoushy\IranianNationalId\Exception\NationalIdException را پیادهسازی میکنند، پس میتوانید همه را یکجا بگیرید.
کدهای مبهم محل صدور
سه رقم نخست کد ملی، محل صدور شناسنامه را نشان میدهد. اما در دیتاست مرجع، ۱۳ کد به بیش از یک شهر نسبت داده شدهاند. این پکیج در چنین حالتی یکی را حدس نمیزند:
$id = NationalId::from('4830000007'); $id->hasKnownIssuancePlace(); // true $id->hasAmbiguousIssuancePlace(); // true $id->issuancePlace(); // null — عمداً حدس زده نمیشود foreach ($id->issuancePlaces() as $place) { echo $place; // 'ازنا، لرستان' سپس 'چالوس، مازندران' }
اگر کد در دیتاست نباشد، issuancePlaces() آرایه خالی و issuancePlace() مقدار null برمیگرداند. برای تمایز میان «ناشناخته» و «مبهم» از hasKnownIssuancePlace() استفاده کنید.
فهرست کامل کدهای مبهم: 253، 288، 313، 337، 382، 385، 386، 395، 483، 593، 615، 623، 635.
کار مستقیم با دیتاست
use AliHoushy\IranianNationalId\IssuancePlaces; IssuancePlaces::forCode('049'); // [IssuancePlace('049', 'شهرری', 'تهران')] IssuancePlaces::forCode('۴۹'); // همان — ارقام فارسی و پر شدن با صفر IssuancePlaces::has('999'); // false IssuancePlaces::isAmbiguous('483'); // true IssuancePlaces::codes(); // فهرست ۵۹۶ کد شناختهشده IssuancePlaces::ambiguousCodes(); // فهرست ۱۳ کد مبهم // جستوجوی وارونه بر پایه نام شهر IssuancePlaces::findByCity('تبریز');
[!NOTE]
findByCity()تطبیق جزئی انجام میدهد و متن را نرمالسازی نمیکند. برای نمونه جستوجوی «اهر» علاوه بر «اهر»، «شاهرود» را هم برمیگرداند. اگر به تطبیق دقیقتری نیاز دارید، خروجی را خودتان فیلتر کنید.
مرجع API
NationalId
| متد | خروجی | توضیح |
|---|---|---|
NationalId::from(string) |
self |
ساخت نمونه؛ برای ورودی نامعتبر استثنا پرتاب میکند |
NationalId::tryFrom(string) |
?self |
مانند بالا، اما null برمیگرداند |
NationalId::isValid(string) |
bool |
فقط اعتبارسنجی |
NationalId::normalize(string) |
?string |
نرمالسازی بدون سنجش رقم کنترل |
value() |
string |
کد ملی ۱۰ رقمی نرمالشده |
format(string $separator = '-') |
string |
نمایش گروهبندیشده مانند 049-937089-9 |
digits() |
list<int> |
ارقام بهصورت آرایه اعداد |
checkDigit() |
int |
رقم کنترل (رقم دهم) |
issuanceCode() |
string |
کد سهرقمی محل صدور |
issuancePlace() |
?IssuancePlace |
محل صدور؛ در حالت ابهام یا ناشناخته null |
issuancePlaces() |
list<IssuancePlace> |
همه محلهای ممکن |
hasKnownIssuancePlace() |
bool |
آیا کد در دیتاست هست؟ |
hasAmbiguousIssuancePlace() |
bool |
آیا بیش از یک محل دارد؟ |
equals(self) |
bool |
مقایسه بر پایه مقدار |
__toString() |
string |
معادل value() |
IssuancePlace
سه پراپرتی خواندنی code، city و province بههمراه متدهای equals()، toArray() و __toString().
IssuancePlaces
متدهای ایستا: forCode()، has()، isAmbiguous()، codes()، ambiguousCodes() و findByCity().
الگوریتم اعتبارسنجی
۱. ورودی نرمال میشود: ارقام غیرلاتین تبدیل و جداکنندهها حذف میشوند. ۲. طول باید بین ۸ تا ۱۰ رقم باشد و با صفر به ۱۰ رقم میرسد. ۳. کدهایی که همه ارقامشان یکسان است رد میشوند. این کدها رقم کنترل درستی دارند اما در عمل صادر نشدهاند. ۴. مجموع وزنی نُه رقم نخست با وزنهای ۱۰ تا ۲ محاسبه و باقیمانده بر ۱۱ گرفته میشود:
- اگر باقیمانده کوچکتر از ۲ باشد، رقم کنترل باید برابر باقیمانده باشد.
- در غیر این صورت، رقم کنترل باید برابر «۱۱ منهای باقیمانده» باشد.
منبع داده و اصلاحات
دیتاست محل صدور از پروژه persian-tools/persian-tools (مجوز MIT) برداشت شده است. سپاس از نگهدارندگان آن پروژه.
هنگام برداشت، دو مشکل در داده بالادستی پیدا و اصلاح شد:
- استان زنجان در فهرست استانهای منبع وجود نداشت و شهرهای زنجان، ابهر و خرمدره بهاشتباه ذیل «اصفهان» ثبت شده بودند. همچنین ملکان، مرند و میانه که در آذربایجان شرقی هستند، ذیل اصفهان آمده بودند. هر شش مورد اصلاح شد و یک تست اختصاصی جلوی بازگشت این خطا را میگیرد.
- کدهای دارای بیش از یک محل در منبع روی هم بازنویسی میشدند. اینجا هر دو محل نگه داشته و بهصورت صریح گزارش میشوند.
[!NOTE] استان البرز در سال ۱۳۸۹ از تهران جدا شد. کدهای کرج، ساوجبلاغ، نظرآباد و طالقان عمداً ذیل «تهران» نگه داشته شدهاند، چون کد محل صدور وضعیت تقسیمات کشوری را در زمان صدور شناسنامه نشان میدهد، نه امروز.
دیتاست وضعیت اداری زمان صدور را بازتاب میدهد و مرجع رسمی سازمان ثبت احوال نیست. اگر خطایی دیدید، لطفاً Issue باز کنید.
تست
composer test # اجرای تستها composer lint # بررسی سبک کد (PSR-12 با Pint) composer analyse # آنالیز ایستا با PHPStan سطح ۹ composer check # هر سه مورد بالا
مشارکت
از مشارکت شما استقبال میکنیم. لطفاً پیش از ارسال Pull Request فایل CONTRIBUTING.md را بخوانید.
امنیت
اگر آسیبپذیری امنیتی پیدا کردید، لطفاً بهجای ثبت Issue عمومی، طبق راهنمای SECURITY.md گزارش دهید.
تغییرات
فهرست تغییرات هر نسخه در CHANGELOG.md نگهداری میشود.
مجوز
این پروژه تحت مجوز MIT منتشر شده است.
English
Validate Iranian national IDs (کد ملی) with the official mod-11 checksum and resolve the issuing province and city.
A framework-agnostic, pure-PHP package with zero runtime dependencies. Works the same in Laravel, Symfony, Slim, or no framework at all.
composer require alihoushy/iranian-national-id
use AliHoushy\IranianNationalId\NationalId; NationalId::isValid('0499370899'); // true $id = NationalId::from('۰۴۹-۹۳۷۰۸۹-۹'); // Persian digits and separators are accepted $id->value(); // '0499370899' $id->issuancePlace()?->city; // 'شهرری' $id->issuancePlace()?->province; // 'تهران'
Thirteen three-digit prefixes map to more than one city in the reference dataset. Rather than guessing,
issuancePlace() returns null for those and issuancePlaces() returns every candidate — use
hasAmbiguousIssuancePlace() to tell an ambiguous prefix apart from an unknown one.
The issuance-place dataset is derived from persian-tools/persian-tools (MIT), with six upstream province mislabels corrected — see the Persian section above for details.
Requires PHP 8.1+ and ext-mbstring. Released under the MIT license.
The full documentation above is written in Persian, the primary language of this package's community.