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

Validator::validateCheckDigit()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

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