alihoushy/iranian-national-id

Validate Iranian national ID (کد ملی) with the official mod-11 checksum and resolve the issuing province and city.

Maintainers

Package info

github.com/alihoushy/iranian-national-id

pkg:composer/alihoushy/iranian-national-id

Transparency log

Statistics

Installs: 2

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 2

v0.1.0 2026-07-28 06:46 UTC

This package is auto-updated.

Last update: 2026-07-28 07:07:03 UTC


README

کد ملی ایران

نسخه در Packagist تست‌ها کیفیت کد تعداد نصب مجوز

اعتبارسنجی کد ملی ایران با الگوریتم رسمی 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.