Completed
Pull Request — master (#12)
by Antonio
01:42
created

Validator::getBirthDate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
3
namespace CodiceFiscale;
4
5
/**
6
 * Description of Validator
7
 *
8
 * @author Antonio Turdo <[email protected]>
9
 */
10
class Validator extends AbstractCalculator
11
{
12
    private $regexs = array(
13
        0 => '/^[a-z]{6}[0-9]{2}[a-z][0-9]{2}[a-z][0-9]{3}[a-z]$/i',
14
        1 => '/^[a-z]{6}[0-9]{2}[a-z][0-9]{2}[a-z][0-9]{2}[a-z]{2}$/i',
15
        2 => '/^[a-z]{6}[0-9]{2}[a-z][0-9]{2}[a-z][0-9][a-z]{3}$/i',
16
        3 => '/^[a-z]{6}[0-9]{2}[a-z][0-9]{2}[a-z]{5}$/i',
17
        4 => '/^[a-z]{6}[0-9]{2}[a-z][0-9][a-z]{6}$/i',
18
        5 => '/^[a-z]{6}[0-9]{2}[a-z]{8}$/i',
19
        6 => '/^[a-z]{6}[0-9][a-z]{9}$/i',
20
        7 => '/^[a-z]{16}$/i',
21
    );
22
    private $omocodiaPositions = array(14, 13, 12, 10, 9, 7, 6);
23
    
24
    private $codiceFiscale;
25
    private $omocodiaAllowed = true;
26
    private $century = null;
27
    
28
    private $foundOmocodiaLevel = null;
29
    private $codiceFiscaleWithoutOmocodia = null;
30
    private $birthDate = null;
31
    private $gender = null;
32
    
33
    private $error = null;
34
    private $isValid = false;
35
36
    /**
37
     * Create a Validator instance.
38
     *
39
     * @param string $codiceFiscale the codice fiscale to validate
40
     * @param array $properties  An array with additional properties.
41
     */
42 22
    public function __construct($codiceFiscale, $properties = array())
43
    {
44 22
        $this->codiceFiscale = strtoupper($codiceFiscale);
45
        
46 22
        if (array_key_exists('omocodiaAllowed', $properties)) {
47 22
            $this->omocodiaAllowed = $properties['omocodiaAllowed'];
48
        }
49
        
50 22
        if (array_key_exists('century', $properties)) {
51 15
            $this->century = $properties['century'];
52
        }
53
        
54
        try {
55 22
            $this->validateLength();
56
57 20
            $this->validateFormat();
58
59 19
            $this->validateCheckDigit();
60
            
61 18
            $this->validateAndReplaceOmocodia();
62
63 18
            $this->validateBirthDateAndGender();
64
65 18
            $this->isValid = true;
66 4
        } catch (\Exception $e) {
67 4
            $this->error = $e->getMessage();
68
        }
69 22
    }
70
71
    /**
72
     * Validates length
73
     *
74
     * @throws \Exception
75
     */
76 22
    private function validateLength()
77
    {
78
        // check empty
79 22
        if (empty($this->codiceFiscale)) {
80 1
            throw new \Exception('The codice fiscale to validate is empty');
81
        }
82
83
        // check length
84 21
        if (strlen($this->codiceFiscale) !== 16) {
85 1
            throw new \Exception('The codice fiscale to validate has an invalid length');
86
        }
87 20
    }
88
89
    /**
90
     * Validates format
91
     *
92
     * @throws \Exception
93
     */
94 20
    private function validateFormat()
95
    {
96 20
        $regexpValid = false;
97 20
        if (!$this->omocodiaAllowed) {
98
            // just one regex
99 1
            if (preg_match($this->regexs[0], $this->codiceFiscale)) {
100 1
                $this->foundOmocodiaLevel = 0;
101 1
                $regexpValid = true;
102
            }
103
        } else {
104
            // all the regex
105 19
            $omocodiaLevelApplied = 0;
106 19
            foreach ($this->regexs as $regex) {
107 19
                if (preg_match($regex, $this->codiceFiscale)) {
108 18
                    $this->foundOmocodiaLevel = $omocodiaLevelApplied;
109 18
                    $regexpValid = true;
110 18
                    break;
111
                }
112 5
                $omocodiaLevelApplied++;
113
            }
114
        }
115
116 20
        if (!$regexpValid) {
117 1
            throw new \Exception('The codice fiscale to validate has an invalid format');
118
        }
119 19
    }
120
    
121
    /**
122
     * Validates check digit
123
     *
124
     * @throws \Exception
125
     */
126 19
    private function validateCheckDigit()
127
    {
128 19
        $checkDigit = $this->calculateCheckDigit($this->codiceFiscale);
129 19
        if ($checkDigit != $this->codiceFiscale[15]) {
130 1
            throw new \Exception('checksum');
131
        }
132 18
    }
133
    
134
    /**
135
     * Validates omocodia and replace with matching chars
136
     *
137
     * @throws \Exception
138
     */
139 18
    private function validateAndReplaceOmocodia()
140
    {
141
        // check and replace omocodie
142 18
        $this->codiceFiscaleWithoutOmocodia = $this->codiceFiscale;
143 18
        for ($omocodiaCheck = 0; $omocodiaCheck < $this->foundOmocodiaLevel; $omocodiaCheck++) {
144 4
            $positionToCheck = $this->omocodiaPositions[$omocodiaCheck];
145 4
            $charToCheck = $this->codiceFiscaleWithoutOmocodia[$positionToCheck];
146 4
            if (!in_array($charToCheck, $this->omocodiaCodes)) {
147
                throw new \Exception('The codice fiscale to validate has an invalid character');
148
            }
149 4
            $this->codiceFiscaleWithoutOmocodia[$positionToCheck] = array_search($charToCheck, $this->omocodiaCodes);
150
        }
151 18
    }
152
    
153
    /**
154
     * Validates birthdate and gender
155
     *
156
     * @throws \Exception
157
     */
158 18
    private function validateBirthDateAndGender()
159
    {
160
        // calculate day and sex
161 18
        $day = (int) substr($this->codiceFiscaleWithoutOmocodia, 9, 2);
162 18
        $this->gender = $day > 40 ? self::CHR_WOMEN : self::CHR_MALE;
163
164 18
        if ($this->gender === self::CHR_WOMEN) {
165 2
            $day -= 40;
166
        }
167
168
        // check day
169 18
        if ($day > 31) {
170
            throw new \Exception('The codice fiscale to validate has invalid characters for birth day');
171
        }
172
173
        // check month
174 18
        $monthChar = substr($this->codiceFiscaleWithoutOmocodia, 8, 1);
175 18
        if (!in_array($monthChar, $this->months)) {
176
            throw new \Exception('The codice fiscale to validate has an invalid character for birth month');
177
        }
178
        
179
        // calculate month
180 18
        $month = array_search($monthChar, $this->months);
181
182
        // calculate year
183 18
        $year = substr($this->codiceFiscaleWithoutOmocodia, 6, 2);
184
185
        // calculate century
186 18
        $currentDate = new \DateTime();
187 18
        $currentYear = $currentDate->format('y');
188 18
        if (!is_null($this->century)) {
189 1
            $century = $this->century;
190
        } else {
191 17
            $currentCentury = substr($currentDate->format('Y'), 0, 2);
192 17
            $century = $year < $currentYear ? $currentCentury : $currentCentury - 1;
193
        }
194
195
        // validate and calculate birth date
196 18
        if (!checkdate($month, $day, $century.$year)) {
197
            throw new \Exception('The codice fiscale to validate has an non existent birth date');
198
        }
199
        
200 18
        $birthDate = new \DateTime();
201 18
        $birthDate->setDate($century.$year, $month, $day);
202 18
        $birthDate->setTime(0, 0, 0);
203
        
204 18
        $this->birthDate = $birthDate->format('Y-m-d');
205 18
    }
206
207
    /**
208
     * Return the validation error
209
     *
210
     * @return string
211
     */
212 7
    public function getError()
213
    {
214 7
        return $this->error;
215
    }
216
217
    /**
218
     * Return true if the provided codice fiscale is valid, false otherwise
219
     *
220
     * @return boolean
221
     */
222 22
    public function isFormallyValid()
223
    {
224 22
        return $this->isValid;
225
    }
226
227
    /**
228
     * Return true if the provided codice fiscale is an omocodia, false otherwise
229
     *
230
     * @return boolean
231
     */
232 1
    public function isOmocodia()
233
    {
234 1
        return $this->foundOmocodiaLevel > 0;
235
    }
236
237
    /**
238
     * Return the provided codice fiscale, cleaned up by omocodia
239
     *
240
     * @return string
241
     */
242 15
    protected function getCodiceFiscaleWithoutOmocodia()
243
    {
244 15
        return $this->codiceFiscaleWithoutOmocodia;
245
    }
246
247
    /**
248
     * Return the birth date
249
     *
250
     * @return \DateTime
251
     */
252 15
    protected function getBirthDate()
253
    {
254 15
        return $this->birthDate;
255
    }
256
257
    /**
258
     * Return the gender
259
     *
260
     * @return string
261
     */
262 15
    protected function getGender()
263
    {
264 15
        return $this->gender;
265
    }
266
}