新公司框架源码的时候,发现了这个功能,于是搜索一番并封装了一下身份证号校验的类。
目前大家的身份证号大多是 18 位的,当然,也不排除有些老人的身份证号是 15 位的。
如果强制要求是 18 位的话,会比较好,因为 15 位的身份证号没有校验码,可以说,只要了解大概结构,随手都可以造出一系列身份证号码来。
当然,如果只是单纯的程序校验, 18 位的身份证号码也可以伪造,就是需要伪造者花点心思。
最好的还是调用相关部门给的接口,进行校验。
本文所编写的身份证号码校验,只是针对相关规则下的计算,是调用接口前能做的事情。
身份证号规则
15位:省份(2位) + 地级市(2位) + 县级市(2位) + 出生年(2位) + 出生月(2位) + 出生日(2位) + 顺序号(3位)
18位:省份(2位) + 地级市(2位) + 县级市(2位) + 出生年(4位) + 出生月(2位) + 出生日(2位) + 顺序号(3位) + 校验位(1位)
相比之下, 18位 比 15位 多出生年 2位 、校验位 1位 。
其中,顺序号如果是偶数,则说明是女生,顺序号是奇数,则说明是男生。
校验位的计算:
有17位数字,分别是:
7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2
分别用身份证的前 17 位乘以上面相应位置的数字,然后相加。
接着用相加的和对 11 取模。
用获得的值在下面 11 个字符里查找对应位置的字符,这个字符就是校验位。
‘1’, ‘0’, ‘X’, ‘9’, ‘8’, ‘7’, ‘6’, ‘5’, ‘4’, ‘3’, ‘2’
15位转18位:
从上述的分析中,可以知道,只要补充上年分和校验位就可以了。
一般情况下年份补充都是加上 19 就可以了。
校验类的实现
通过分析身份证号的规则,了解到,有几点是可以做的:
当然,因为可能部分人用的是 15位 的身份证号,所以需要一个转换的办法,不过,这里还是建议限制需要 18位 的身份证号。
下面开始实现:
初始化:
class IDCardFilter
{
/**
* 身份证号码校验
*
* @param string $idCard
* @return bool
*/
public function vaild($idCard)
{
// 基础的校验,校验身份证格式是否正确
if (!$this- isCardNumber($idCard)) {
return false;
}
// 将 15 位转换成 18 位
$idCard = $this- fifteen2Eighteen($idCard);
// 检查省是否存在
if (!$this- checkProvince($idCard)) {
return false;
}
// 检查生日是否正确
if (!$this- checkBirthday($idCard)) {
return false;
}
// 检查校验码
return $this- checkCode($idCard);
}
}
上面已经实现了一个校验的办法,里面调用了类里的很多办法,下面一一实现。
检测是否是身份证号码:
这一块的处理比较简单,一个正则表达式搞定了。
其中, (^d{15}) 用于匹配 15位 身份证号的情况; (^d{17}(d|X)) 用于匹配 18位 身份证号的情况。
const REGX = '#(^d{15})|(^d{17}(d|X))#';
/**
* 检测是否是身份证号码
*
* @param string $idCard
* @return boolean
*/
public function isCardNumber($idCard)
{
return preg_match(self::REGX, $idCard);
}
15位转18位:
逻辑不复杂,先判断是否是15位,然后判断需要添加的年份,最终生成校验码拼接返回就OK了。
/**
* 15位转18位
*
* @param string $idCard
* @return void
*/
public function fifteen2Eighteen($idCard)
{
if (strlen($idCard) != 15) {
return $idCard;
}
// 如果身份证顺序码是996 997 998 999,这些是为百岁以上老人的特殊编码
// $code = array_search(substr($idCard, 12, 3), [996, 997, 998, 999]) !== false ? '18' : '19';
// 一般 19 就够了
$code = '19';
$idCardBase = substr($idCard, 0, 6) . $code . substr($idCard, 6, 9);
return $idCardBase . $this- genCode($idCardBase);
}
校验码的生成:
详细计算规则见上面,这里就不做重复的阐述了。
/**
* 生成校验码
*
* @param string $idCardBase
* @return void
*/
final protected function genCode($idCardBase)
{
$idCardLength = strlen($idCardBase);
if ($idCardLength != 17) {
return false;
}
$factor = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
$verifyNumbers = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
$sum = 0;
for ($i = 0; $i < $idCardLength; $i++) {
$sum += substr($idCardBase, $i, 1) * $factor[$i];
}
$index = $sum % 11;
return $verifyNumbers[$index];
}
检查省份是否正确:
protected $provinces = [
11 = "北京", 12 = "天津", 13 = "河北", 14 = "山西", 15 = "内蒙古",
21 = "辽宁", 22 = "吉林", 23 = "黑龙江", 31 = "上海", 32 = "江苏",
33 = "浙江", 34 = "安徽", 35 = "福建", 36 = "江西", 37 = "山东", 41 = "河南",
42 = "湖北", 43 = "湖南", 44 = "广东", 45 = "广西", 46 = "海南", 50 = "重庆",
51 = "四川", 52 = "贵州", 53 = "云南", 54 = "西藏", 61 = "陕西", 62 = "甘肃",
63 = "青海", 64 = "宁夏", 65 = "新疆", 71 = "台湾", 81 = "香港", 82 = "澳门", 91 = "国外"
];
/**
* 检查省份是否正确
*
* @param string $idCard
* @return void
*/
public function checkProvince($idCard)
{
$provinceNumber = substr($idCard, 0, 2);
return isset($this- provinces[$provinceNumber]);
}
检测生日是否正确:
这里也是用正则匹配,匹配出年月日的。
/**
* 检测生日是否正确
*
* @param string $idCard
* @return void
*/
public function checkBirthday($idCard)
{
$regx = '#^d{6}(d{4})(d{2})(d{2})d{3}[0-9X]$#';
if (!preg_match($regx, $idCard, $matches)) {
return false;
}
array_shift($matches);
list($year, $month, $day) = $matches;
return checkdate($month, $day, $year);
}
校验码比对:
话说, 15位 转 18位 的都完全不用考虑这个办法了。
/**
* 校验码比对
*
* @param string $idCard
* @return void
*/
public function checkCode($idCard)
{
$idCardBase = substr($idCard, 0, 17);
$code = $this- genCode($idCardBase);
return $idCard == ($idCardBase . $code);
}
完整代码
传送门:IDCardFilter
最后
这个功能最多算是新颖吧,毕竟之前没有接触过。很开心代码片段里又增加了新的成员。
以上所述是小编给大家介绍的PHP校验15位和18位身份证号的类封装,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对网站事(zalou.cn)网站的支持!