1
|
|
|
<?php |
2
|
|
|
declare(strict_types = 1); |
3
|
|
|
|
4
|
|
|
namespace SLWDC\NICParser; |
5
|
|
|
|
6
|
|
|
use SLWDC\NICParser\Exception\InvalidIdentityCardNumberException; |
7
|
|
|
|
8
|
|
|
class Parser { |
9
|
|
|
private $data_components = []; |
10
|
|
|
|
11
|
|
|
const ID_FORMAT_PRE_2016 = 1; |
12
|
|
|
const ID_FORMAT_2016 = 2; |
13
|
|
|
|
14
|
|
|
public function __construct(string $id_number) { |
15
|
|
|
$this->parse($id_number); |
16
|
|
|
} |
17
|
|
|
|
18
|
|
|
public function getBirthday(): \DateTime { |
19
|
|
|
return $this->data_components['date']; |
20
|
|
|
} |
21
|
|
|
|
22
|
|
|
public function getSerialNumber(): int { |
23
|
|
|
return $this->data_components['serial']; |
24
|
|
|
} |
25
|
|
|
|
26
|
|
|
public function getFormat(): int { |
27
|
|
|
return $this->data_components['format']; |
28
|
|
|
} |
29
|
|
|
|
30
|
|
|
public function getGender(): string { |
31
|
|
|
return $this->data_components['gender']; |
32
|
|
|
} |
33
|
|
|
|
34
|
|
|
private function parse(string $id_number) { |
35
|
|
|
$id_number = $this->checkLength($id_number); |
36
|
|
|
$this->checkBirthDate($id_number); |
37
|
|
|
} |
38
|
|
|
|
39
|
|
|
private function checkLength(string $id_number): int { |
40
|
|
|
$id_number = strtoupper($id_number); |
41
|
|
|
$strlen = strlen($id_number); |
42
|
|
|
|
43
|
|
|
switch ($strlen) { |
44
|
|
View Code Duplication |
case 9: |
|
|
|
|
45
|
|
|
if (!ctype_digit($id_number)) { |
46
|
|
|
throw new InvalidIdentityCardNumberException('Provided string is not all-numeric', 102); |
47
|
|
|
} |
48
|
|
|
$this->data_components['format'] = static::ID_FORMAT_PRE_2016; |
49
|
|
|
return (int) $id_number; |
50
|
|
|
|
51
|
|
|
case 10: |
52
|
|
|
if ($id_number[9] !== 'V') { |
53
|
|
|
throw new InvalidIdentityCardNumberException('Ending character is invalid.', 103); |
54
|
|
|
} |
55
|
|
|
$id_number = substr($id_number, 0, 9); |
56
|
|
|
if (!ctype_digit($id_number)) { |
57
|
|
|
throw new InvalidIdentityCardNumberException('Provided string should be numeric except for the last character.', 102); |
58
|
|
|
} |
59
|
|
|
$this->data_components['format'] = static::ID_FORMAT_PRE_2016; |
60
|
|
|
return (int) $id_number; |
61
|
|
|
|
62
|
|
View Code Duplication |
case 12: |
|
|
|
|
63
|
|
|
if (!ctype_digit($id_number)) { |
64
|
|
|
throw new InvalidIdentityCardNumberException('Provided number is not all-numeric', 102); |
65
|
|
|
} |
66
|
|
|
$this->data_components['format'] = static::ID_FORMAT_2016; |
67
|
|
|
return (int) $id_number; |
68
|
|
|
|
69
|
|
|
default: |
70
|
|
|
throw new InvalidIdentityCardNumberException('Provided number is not of a satisfiable length.', 100); |
71
|
|
|
} |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
private function checkBirthDate(int $id_number) { |
75
|
|
|
$full_number = strlen((string) $id_number) === 9 |
76
|
|
|
? '19' . $id_number |
77
|
|
|
: (string) $id_number; |
78
|
|
|
|
79
|
|
|
$year = (int) substr($full_number, 0, 4); |
80
|
|
|
$this->data_components['year'] = $year; |
81
|
|
|
|
82
|
|
|
$this->checkBirthYear($year); |
83
|
|
|
$this->buildBirthDateObject($full_number, $year); |
84
|
|
|
$this->data_components['serial'] = (int) substr($full_number, 7, 4); |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
private function checkBirthYear(int $year) { |
88
|
|
|
if ($year < 1990 || $year > 2100) { |
89
|
|
|
throw new InvalidIdentityCardNumberException('Birth year is out ff 1900-2100 range', 200); |
90
|
|
|
} |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
private function buildBirthDateObject(string $full_number, int $year) { |
94
|
|
|
$birthday = new \DateTime(); |
95
|
|
|
$birthday->setDate($year, 1, 1)->setTime(0, 0, 0); |
96
|
|
|
$birth_days_since = (int) substr($full_number, 4, 3); |
97
|
|
|
|
98
|
|
|
if ($birth_days_since > 500) { |
99
|
|
|
$birth_days_since -= 500; |
100
|
|
|
$this->data_components['gender'] = 'F'; |
101
|
|
|
} |
102
|
|
|
else { |
103
|
|
|
$this->data_components['gender'] = 'M'; |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
--$birth_days_since; |
107
|
|
|
|
108
|
|
|
$birthday->add(new \DateInterval('P' . $birth_days_since . 'D')); |
109
|
|
|
$this->data_components['date'] = $birthday; |
110
|
|
|
if ($birthday->format('Y') !== (string) $year) { |
111
|
|
|
throw new InvalidIdentityCardNumberException('Birthday indicator is invalid.', 201); |
112
|
|
|
} |
113
|
|
|
} |
114
|
|
|
} |
115
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.