diff --git a/src/Cnp.php b/src/Cnp.php index c3f07ea..36d2ab6 100644 --- a/src/Cnp.php +++ b/src/Cnp.php @@ -1,316 +1,333 @@ -isValid()) { - * // extract info from CNP - * echo "CNP {$cnpToValidate} - is valid" . PHP_EOL; - * echo "Birth Date: {$cnp->getBirthDateFromCNP('Y/m/d')}" . PHP_EOL; - * echo "Birth Place: {$cnp->getBirthCountyFromCNP()}" . PHP_EOL; - * echo "Gender: {$cnp->getGenderFromCNP('male', 'female')}" . PHP_EOL; - * } else { - * echo "CNP {$cnpToValidate} is invalid" . PHP_EOL; - * } - * ``` - * - * @see https://ro.wikipedia.org/wiki/Cod_numeric_personal - * @author Niku Alcea - * - * @property boolean $_isValid - * @property array $_cnp - * @property integer|false $_year - * @property integer|false $_month - * @property integer|false $_day - * @property integer|false $_cc - * @property integer|false $_serial - */ -class Cnp -{ - private $_isValid = false; - private $_cnp = []; - private $_year = false; - private $_month = false; - private $_day = false; - private $_cc = false; - private $_serial = false; - private static $controlKey = [2, 7, 9, 1, 4, 6, 3, 5, 8, 2, 7, 9]; - private static $countyCode = [ - '01' => 'Alba', - '02' => 'Arad', - '03' => 'Arges', - '04' => 'Bacau', - '05' => 'Bihor', - '06' => 'Bistrita-Nasaud', - '07' => 'Botosani', - '08' => 'Brasov', - '09' => 'Braila', - '10' => 'Buzau', - '11' => 'Caras-Severin', - '12' => 'Cluj', - '13' => 'Constanta', - '14' => 'Covasna', - '15' => 'Dambovita', - '16' => 'Dolj', - '17' => 'Galati', - '18' => 'Gorj', - '19' => 'Harghita', - '20' => 'Hunedoara', - '21' => 'Ialomita', - '22' => 'Iasi', - '23' => 'Ilfov', - '24' => 'Maramures', - '25' => 'Mehedinti', - '26' => 'Mures', - '27' => 'Neamt', - '28' => 'Olt', - '29' => 'Prahova', - '30' => 'Satu Mare', - '31' => 'Salaj', - '32' => 'Sibiu', - '33' => 'Suceava', - '34' => 'Teleorman', - '35' => 'Timis', - '36' => 'Tulcea', - '37' => 'Vaslui', - '38' => 'Valcea', - '39' => 'Vrancea', - '40' => 'Bucuresti', - '41' => 'Bucuresti S.1', - '42' => 'Bucuresti S.2', - '43' => 'Bucuresti S.3', - '44' => 'Bucuresti S.4', - '45' => 'Bucuresti S.5', - '46' => 'Bucuresti S.6', - '51' => 'Calarasi', - '52' => 'Giurgiu' - ]; - - /** - * CNP constructor. - * @param string $cnp - */ - public function __construct($cnp) - { - $this->_cnp = str_split(trim($cnp)); - $this->_isValid = $this->validateCnp(); - } - - /** - * Validation for Romanian Social Security Number (CNP) - * @return bool - */ - private function validateCnp() - { - $cnpArray = $this->_cnp; - - // CNP must have 13 characters - if (count($cnpArray) != 13) { - return false; - } - - // Set and check year, month, day and county - if ($this->year() && $this->month() && $this->day() && $this->county()) { - $hashArray = self::$controlKey; - $hashSum = 0; - // All characters must be numeric - for ($i = 0; $i <= 12; $i++) { - if (!is_numeric($cnpArray[$i])) { - return false; - } - if ($i < 12) { - $hashSum += (int)$cnpArray[$i] * (int)$hashArray[$i]; - } - } - - $hashSum = $hashSum % 11; - if ($hashSum == 10) { - $hashSum = 1; - } - - return ($cnpArray[12] == $hashSum); - } - - return false; - } - - /** - * Check and set year - * @return boolean - */ - private function year() - { - $cnp = $this->_cnp; - $year = ($cnp[1] * 10) + $cnp[2]; - switch ($cnp[0]) { - // romanian citizen born between 1900.01.01 and 1999.12.31 - case 1 : - case 2 : - $year += 1900; - break; - // romanian citizen born between 1800.01.01 and 1899.12.31 - case 3 : - case 4 : - $year += 1800; - break; - // romanian citizen born between 2000.01.01 and 2099.12.31 - case 5 : - case 6 : - $year += 2000; - break; - // residents && people with foreign citizenship - case 7 : - case 8 : - case 9 : - $year += 2000; - if ($year > (int)date('Y') - 14) { - $year -= 100; - } - break; - default : - $year = 0; - break; - } - - $this->_year = $year; - - return ($year >= 1800) && ($year <= 2099); - } - - /** - * Check and set month - * @return boolean - */ - private function month() - { - $this->_month = (int)($this->_cnp[3] . $this->_cnp[4]); - - return ($this->_month >= 1) && ($this->_month <= 12); - } - - /** - * Check and set day - * @return boolean - */ - private function day() - { - $this->_day = (int)($this->_cnp[5] . $this->_cnp[6]); - - if (($this->_day < 1) || ($this->_day > 31)) { - return false; - } - - if ($this->_day > 28) { - // validate date for day of month - 28, 29, 30 si 31 - if (checkdate($this->_month, $this->_day, $this->_year) === false) { - return false; - } - } - - return true; - } - - /** - * Check and set county code - * @return boolean - */ - private function county() - { - $this->_cc = (string)($this->_cnp[7] . $this->_cnp[8]); - - return array_key_exists($this->_cc, self::$countyCode); - } - - /** - * - * @return boolean - */ - public function isValid() - { - return $this->_isValid; - } - - /** - * Get Birth Place from Romanian Social Security Number (CNP) - * @param string|bool $defaultReturn - * @return string|bool - */ - public function getBirthCountyFromCNP($defaultReturn = false) - { - if ($this->_isValid) { - return self::$countyCode[$this->_cc]; - } - - return $defaultReturn; - } - - /** - * Get Birth Date from Romanian Social Security Number (CNP) - * @param string $format - * @return string|boolean - */ - public function getBirthDateFromCNP($format = 'Y-m-d') - { - if ($this->_isValid) { - return \DateTime::createFromFormat('Y-m-d', "{$this->_year}-{$this->_month}-{$this->_day}")->format($format); - } - - return false; - } - - /** - * Get gender from Romanian Social Security Number (CNP) - * @param string $m - * @param string $f - * @return string|boolean - */ - public function getGenderFromCNP($m = 'M', $f = 'F') - { - if ($this->_isValid) { - if (in_array($this->_cnp[0], [1, 3, 5, 7])) { - return $m; - } elseif (in_array($this->_cnp[0], [2, 4, 6, 8])) { - return $f; - } - } - - return false; - } - - /** - * @return bool|string - */ - public function getSerialNumberFromCNP() - { - if ($this->_isValid) { - return $this->_cnp[9] . $this->_cnp[10] . $this->_cnp[11]; - } - - return false; - } - -} +isValid()) { + * // extract info from CNP + * echo "CNP {$cnpToValidate} - is valid" . PHP_EOL; + * echo "Birth Date: {$cnp->getBirthDateFromCNP('Y/m/d')}" . PHP_EOL; + * echo "Birth Place: {$cnp->getBirthCountyFromCNP()}" . PHP_EOL; + * echo "Gender: {$cnp->getGenderFromCNP('male', 'female')}" . PHP_EOL; + * } else { + * echo "CNP {$cnpToValidate} is invalid" . PHP_EOL; + * } + * + * // or + * + * echo "CNP {$cnpToValidate} is " () . Cnp::validate($cnpToValidate) ? 'valid' : 'invalid'; + * + * ``` + * + * @see https://ro.wikipedia.org/wiki/Cod_numeric_personal + * @author Niku Alcea + * + * @property boolean $_isValid + * @property array $_cnp + * @property integer|false $_year + * @property integer|false $_month + * @property integer|false $_day + * @property integer|false $_cc + */ +class Cnp +{ + private $_isValid = false; + private $_cnp = []; + private $_year = false; + private $_month = false; + private $_day = false; + private $_cc = false; + + private static $controlKey = [2, 7, 9, 1, 4, 6, 3, 5, 8, 2, 7, 9]; + private static $countyCode = [ + '01' => 'Alba', + '02' => 'Arad', + '03' => 'Arges', + '04' => 'Bacau', + '05' => 'Bihor', + '06' => 'Bistrita-Nasaud', + '07' => 'Botosani', + '08' => 'Brasov', + '09' => 'Braila', + '10' => 'Buzau', + '11' => 'Caras-Severin', + '12' => 'Cluj', + '13' => 'Constanta', + '14' => 'Covasna', + '15' => 'Dambovita', + '16' => 'Dolj', + '17' => 'Galati', + '18' => 'Gorj', + '19' => 'Harghita', + '20' => 'Hunedoara', + '21' => 'Ialomita', + '22' => 'Iasi', + '23' => 'Ilfov', + '24' => 'Maramures', + '25' => 'Mehedinti', + '26' => 'Mures', + '27' => 'Neamt', + '28' => 'Olt', + '29' => 'Prahova', + '30' => 'Satu Mare', + '31' => 'Salaj', + '32' => 'Sibiu', + '33' => 'Suceava', + '34' => 'Teleorman', + '35' => 'Timis', + '36' => 'Tulcea', + '37' => 'Vaslui', + '38' => 'Valcea', + '39' => 'Vrancea', + '40' => 'Bucuresti', + '41' => 'Bucuresti S.1', + '42' => 'Bucuresti S.2', + '43' => 'Bucuresti S.3', + '44' => 'Bucuresti S.4', + '45' => 'Bucuresti S.5', + '46' => 'Bucuresti S.6', + '51' => 'Calarasi', + '52' => 'Giurgiu' + ]; + + /** + * CNP constructor. + * @param string|int $cnp + */ + public function __construct($cnp) + { + try { + $this->_cnp = str_split(trim($cnp)); + $this->_isValid = $this->validateCnp(); + } catch (\Throwable $e) { + $this->_isValid = false; + } + } + + /** + * @param string|int $cnp + * @return bool + */ + public static function validate($cnp) + { + return (new static($cnp))->isValid(); + } + + /** + * Validation for Romanian Social Security Number (CNP) + * @return bool + */ + private function validateCnp() + { + $cnpArray = $this->_cnp; + + // CNP must have 13 characters + if (count($cnpArray) != 13) { + return false; + } + + // Set and check year, month, day and county + if ($this->year() && $this->month() && $this->day() && $this->county()) { + $hashArray = self::$controlKey; + $hashSum = 0; + // All characters must be numeric + for ($i = 0; $i <= 12; $i++) { + if (!is_numeric($cnpArray[$i])) { + return false; + } + if ($i < 12) { + $hashSum += (int)$cnpArray[$i] * (int)$hashArray[$i]; + } + } + + $hashSum = $hashSum % 11; + if ($hashSum == 10) { + $hashSum = 1; + } + + return ($cnpArray[12] == $hashSum); + } + + return false; + } + + /** + * Check and set year + * @return boolean + */ + private function year() + { + $cnp = $this->_cnp; + $year = ($cnp[1] * 10) + $cnp[2]; + switch ($cnp[0]) { + // romanian citizen born between 1900.01.01 and 1999.12.31 + case 1 : + case 2 : + $year += 1900; + break; + // romanian citizen born between 1800.01.01 and 1899.12.31 + case 3 : + case 4 : + $year += 1800; + break; + // romanian citizen born between 2000.01.01 and 2099.12.31 + case 5 : + case 6 : + $year += 2000; + break; + // residents && people with foreign citizenship + case 7 : + case 8 : + case 9 : + $year += 2000; + if ($year > (int)date('Y') - 14) { + $year -= 100; + } + break; + default : + $year = 0; + break; + } + + $this->_year = $year; + + return ($year >= 1800) && ($year <= 2099); + } + + /** + * Check and set month + * @return boolean + */ + private function month() + { + $this->_month = (int)($this->_cnp[3] . $this->_cnp[4]); + + return ($this->_month >= 1) && ($this->_month <= 12); + } + + /** + * Check and set day + * @return boolean + */ + private function day() + { + $this->_day = (int)($this->_cnp[5] . $this->_cnp[6]); + + if (($this->_day < 1) || ($this->_day > 31)) { + return false; + } + + if ($this->_day > 28) { + // validate date for day of month - 28, 29, 30 si 31 + if (checkdate($this->_month, $this->_day, $this->_year) === false) { + return false; + } + } + + return true; + } + + /** + * Check and set county code + * @return boolean + */ + private function county() + { + $this->_cc = (string)($this->_cnp[7] . $this->_cnp[8]); + + return array_key_exists($this->_cc, self::$countyCode); + } + + /** + * + * @return boolean + */ + public function isValid() + { + return $this->_isValid; + } + + /** + * Get Birth Place from Romanian Social Security Number (CNP) + * @param string|bool $defaultReturn + * @return string|bool + */ + public function getBirthCountyFromCNP($defaultReturn = false) + { + if ($this->_isValid) { + return self::$countyCode[$this->_cc]; + } + + return $defaultReturn; + } + + /** + * Get Birth Date from Romanian Social Security Number (CNP) + * @param string $format + * @return string|boolean + */ + public function getBirthDateFromCNP($format = 'Y-m-d') + { + if ($this->_isValid) { + return \DateTime::createFromFormat('Y-m-d', "{$this->_year}-{$this->_month}-{$this->_day}")->format($format); + } + + return false; + } + + /** + * Get gender from Romanian Social Security Number (CNP) + * @param string $m + * @param string $f + * @return string|boolean + */ + public function getGenderFromCNP($m = 'M', $f = 'F') + { + if ($this->_isValid) { + if (in_array($this->_cnp[0], [1, 3, 5, 7])) { + return $m; + } elseif (in_array($this->_cnp[0], [2, 4, 6, 8])) { + return $f; + } + } + + return false; + } + + /** + * @return bool|string + */ + public function getSerialNumberFromCNP() + { + if ($this->_isValid) { + return $this->_cnp[9] . $this->_cnp[10] . $this->_cnp[11]; + } + + return false; + } + +} diff --git a/tests/CnpTest.php b/tests/CNPTest.php similarity index 74% rename from tests/CnpTest.php rename to tests/CNPTest.php index 6e35d0b..62f7b99 100644 --- a/tests/CnpTest.php +++ b/tests/CNPTest.php @@ -3,29 +3,41 @@ use alcea\cnp\Cnp; use PHPUnit\Framework\TestCase; -final class CnpTest extends TestCase +final class CNPTest extends TestCase { public function cnpProvider() { return [ - //[CNP, isValid, year, month, day, county, serial] + // CNP, isValid, year, month, day, county, serial + + // valid CNP [6140101070075, true, 2014, 1, 1, 'F', 'Botosani', '007'], ['6140101070075', true, 2014, 1, 1, 'F', 'Botosani', '007'], [' 6140101070075', true, 2014, 1, 1, 'F', 'Botosani', '007'], [2331214442371, true, 1933, 12, 14, 'F', 'Bucuresti S.4', '237'], [1960911123653, true, 1996, 9, 11, 'M', 'Cluj', '365'], [3970908055828, true, 1897, 9, 8, 'M', 'Bihor', '582'], + + // invalid CNP [1960911123655, false, false, false, false, false, false, false], ['123', false, false, false, false, false, false, false], - ['614010107007A ', false, false, false, false, false, false, false] + ['614010107007A ', false, false, false, false, false, false, false], + [false, false, false, false, false, false, false, false], + [true, false, false, false, false, false, false, false], + [0, false, false, false, false, false, false, false], + [-1, false, false, false, false, false, false, false], + ['', false, false, false, false, false, false, false], + ['xxx', false, false, false, false, false, false, false], + [[], false, false, false, false, false, false, false], + [new stdClass(), false, false, false, false, false, false, false], ]; } /** * @dataProvider cnpProvider */ - public function testCNP($cnp, $isValid, $year, $month, $day, $sex, $county, $serial) + public function test_CNP_is_valid_or_not($cnp, $isValid, $year, $month, $day, $sex, $county, $serial) { $_cnp = new Cnp($cnp); @@ -38,6 +50,14 @@ public function testCNP($cnp, $isValid, $year, $month, $day, $sex, $county, $ser $this->assertEquals($_cnp->getSerialNumberFromCNP(), $serial); } + /** + * @dataProvider cnpProvider + */ + public function test_static_CNP_validator($cnp, $isValid, $year, $month, $day, $sex, $county, $serial) + { + $this->assertEquals($isValid, Cnp::validate($cnp)); + } + /** * TESTCASE - CNP 2910627308894 * BirthDate: [ @@ -52,7 +72,7 @@ public function testCNP($cnp, $isValid, $year, $month, $day, $sex, $county, $ser * * @test */ - public function testCNP_2910627308894_IsValid() + public function testCNP_2910627308894_is_valid() { $cnp = new Cnp(2910627308894); @@ -80,7 +100,7 @@ public function testCNP_2910627308894_IsValid() * * @test */ - public function testCNP_2890905230065_IsValid() + public function testCNP_2890905230065_is_valid() { $cnp = new Cnp(2890905230065); @@ -100,7 +120,7 @@ public function testCNP_2890905230065_IsValid() /** * @test */ - public function testCNP_22222_IsInvalid() + public function testCNP_22222_is_invalid() { $cnp = new Cnp(22222); @@ -109,5 +129,4 @@ public function testCNP_22222_IsInvalid() $this->assertFalse($cnp->getBirthDateFromCNP()); $this->assertFalse($cnp->getGenderFromCNP()); } - }