رفتن به متن راهنما
همه‌ی راهنماها

کد ملی

اعتبارسنجی کد ملی ایران با رقم کنترل پیمانهٔ ۱۱، پذیرش رقم‌های فارسی و عربی و خواندن سرنخ محل صدور.

برنامه RTLY-Kit 0.2.0آخرین بررسی زمان خواندن 5 دقیقه

در این صفحه

شروع سریع

PHP
<?php
require 'vendor/autoload.php';

use RtlyKit\Validation\NationalCode;

var_dump(NationalCode::isValid('0499370899'));      // bool(true)
var_dump(NationalCode::isValid('۰۴۹۹۳۷۰۸۹۹'));      // bool(true)، رقم‌های فارسی
var_dump(NationalCode::isValid('049-937-0899'));    // bool(true)، خط تیره حذف می‌شود

$result = NationalCode::validate('0499370899');
echo json_encode($result->details(), JSON_UNESCAPED_UNICODE);
// {"normalized":"0499370899","location":{"province":"تهران","city":"شهرری"}}

این کلاس قرارداد Validator را دارد. پس validate() یک Result می‌دهد و برای ورودی بد خطا پرتاب نمی‌کند. توابع کمکی RtlyKit\is_national_code() و RtlyKit\validate_national_code() هستند:

PHP
use function RtlyKit\is_national_code;
use function RtlyKit\validate_national_code;

var_dump(is_national_code('۰۴۹۹۳۷۰۸۹۹'));                  // bool(true)
print_r(validate_national_code('0499370898')->errors());  // Array ( [0] => invalid_checksum )

چه چیزی بررسی می‌شود

بررسی‌ها به همین ترتیب انجام می‌شوند و با اولین مشکل متوقف می‌شوند:

  1. نوع و اندازه. رشته، int و float ِ صحیح قبول می‌شود. چیز دیگر invalid_type می‌دهد و رشتهٔ بیشتر از ۴۰۹۶ بایت input_too_long .
  2. یکسان‌سازی. رقم‌های فارسی و عربی انگلیسی می‌شوند. فاصله، خط تیره و نیم‌فاصله (ZWNJ) حذف می‌شوند و دو سر رشته تمیز می‌شود.
  3. قالب. دقیقاً ۱۰ رقم، وگرنه invalid_format .
  4. رقم‌های تکراری. 0000000000 تا 9999999999 در حساب جور درمی‌آیند، ولی کد واقعی نیستند. برای همین repeated_digits می‌دهند.
  5. رقم کنترل (قاعدهٔ پیمانهٔ ۱۱ در بخش بعد)، وگرنه invalid_checksum .

الگوریتم رقم کنترل

نُه رقم اول را به ترتیب در وزن‌های ۱۰ تا ۲ ضرب کنید و جمع بزنید. باقیماندهٔ جمع بر ۱۱ را بگیرید. اگر باقیمانده ۰ یا ۱ بود، رقم کنترل خود باقیمانده است. در غیر این صورت رقم کنترل ۱۱ منهای باقیمانده است.

برای 0499370899 حاصل‌ضرب‌ها ۰، ۳۶، ۷۲، ۶۳، ۱۸، ۳۵، ۰، ۲۴ و ۱۸ هستند و جمعشان ۲۶۶ است. باقیمانده ۲۶۶ بر ۱۱ برابر ۲ است. پس رقم کنترل باید ۱۱ − ۲ = ۹ باشد، و رقم آخر همین است.

PHP
print_r(NationalCode::validate('0499370898')->errors());   // [invalid_checksum]
print_r(NationalCode::validate('1111111111')->errors());   // [repeated_digits]
print_r(NationalCode::validate('12345')->errors());        // [invalid_format]
print_r(NationalCode::validate('abc')->errors());          // [invalid_format]

رشته بماند. کد ملی می‌تواند با صفر شروع شود. NationalCode::validate(499370899) فقط نه رقم 499370899 را می‌بیند و invalid_format می‌دهد. بین ورودی و اعتبارسنج (از جمله در ستون پایگاه داده یا عدد JSON) مقدار را به عدد صحیح تبدیل نکنید.

ورودی‌هایی که قبول می‌شوند

ورودینتیجه
'0499370899'معتبر
'۰۴۹۹۳۷۰۸۹۹' (رقم‌های فارسی)معتبر
'٠٤٩٩٣٧٠٨٩٩' (رقم‌های عربی-هندی)معتبر
'049-937-0899'، ' 0499370899 'معتبر. جداکننده و فاصله حذف می‌شود
null، []، 1.5invalid_type
12.0مثل '12' خوانده می‌شود، پس invalid_format
۴۰۹۷ نویسهinput_too_long

فقط فاصله، خط تیره و ZWNJ جداکننده حساب می‌شوند. اسلش، نقطه و حرف حذف نمی‌شوند و invalid_format می‌دهند.

اگر فقط شکل تمیز را می‌خواهید، بدون اعتبارسنجی، از NationalCode::normalize() استفاده کنید:

PHP
echo NationalCode::normalize(' ۰۴۹-۹۳۷ ۰۸۹۹ ');   // 0499370899

جزئیات Result

کلیدنوعمعنا
normalizedرشتهکد بعد از یکسان‌سازی (برای invalid_type و input_too_long خالی است)
locationآرایه یا nullبرای کد معتبری که پیش‌شمارهٔ آن در جدول هست {province, city} است، وگرنه null

کدهای خطا: invalid_format، repeated_digits، invalid_checksum، invalid_type و input_too_long. معنی هر کدام در جدول کدهای خطا است.

سرنخ محل صدور

سه رقم اول کد ملی پیش‌شماره‌ای است که ثبت احوال داده است. NationalCode::getLocation() آن را به استان و شهر وصل می‌کند. اگر کد نامعتبر باشد یا پیش‌شماره در جدول نباشد، null می‌دهد:

PHP
print_r(NationalCode::getLocation('0499370899'));
// Array ( [province] => تهران [city] => شهرری )

var_dump(NationalCode::getLocation('0499370898'));   // NULL (کد نامعتبر)

$a = NationalCode::validate('0012345679');   // پیش‌شمارهٔ 001
echo json_encode($a->details()['location'], JSON_UNESCAPED_UNICODE);
// {"province":"تهران","city":"تهران مرکزی"}

$b = NationalCode::validate('9991234561');   // رقم کنترل درست، پیش‌شمارهٔ 999 در جدول نیست
var_dump($b->isValid(), $b->details()['location']);   // bool(true)  NULL

سرنخ است، نه مدرک. پیش‌شماره نشان می‌دهد شناسنامه یا کارت اولیه کجا صادر شده است، نه محل تولد یا سکونت. خیلی از آدم‌ها کدی دارند که جای دیگری صادر شده، و مرز استان‌ها هم بعد از صدور عوض شده (مثلاً کرج زیر تهران آمده). از محل صدور برای احراز هویت یا سکونت استفاده نکنید.

خوب است بدانید. ثبت احوال فهرست ماشین‌خوان منتشر نمی‌کند. جدول ما ۵۴۷ پیش‌شماره دارد. هر مورد وقتی آمده که سه مجموعه‌دادهٔ عمومی دربارهٔ استان و شهرش هم‌نظر بوده‌اند و چهارمی مخالفتی نداشته. این مجموعه‌ها ریشهٔ مشترک دارند. پس توافقشان قوی است، ولی کاملاً مستقل نیست. اگر پیش‌شماره‌ای در جدول نباشد، یعنی ناشناخته است، نه نامعتبر. منبع‌ها در دقت و داده است.

اعتبارسنجی چه چیزی را نمی‌گوید

  • رقم کنترل درست یعنی عدد خوش‌قالب است. نمی‌گوید این کد واقعاً صادر شده یا مال کسی است.
  • شخص حقوقی شناسهٔ ۱۱ رقمیِ جدا دارد. این اعتبارسنج آن را پوشش نمی‌دهد و با invalid_format رد می‌کند.
  • شمارهٔ شناسایی اتباع خارجی (مثل آمایش) طرح دیگری دارد و پوشش داده نمی‌شود.

در فرم

پیام را از روی کد خطا بسازید و مقدار یکسان‌شده را ذخیره کنید:

PHP
$messages = [
    'invalid_format'   => 'کد ملی باید ۱۰ رقم باشد.',
    'repeated_digits'  => 'این کد ملی معتبر نیست.',
    'invalid_checksum' => 'این کد ملی معتبر نیست.',
];

$result = NationalCode::validate($_POST['national_code'] ?? null);
if (! $result->isValid()) {
    $key = $result->errors()[0];
    echo $messages[$key] ?? 'ورودی نامعتبر است.';
} else {
    $code = $result->details()['normalized'];   // همین را ذخیره کنید
}

در Laravel از قانون و ترجمه‌های آماده استفاده کنید. اعتبارسنجی و cast در Laravel را ببینید.