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

Validator::__construct()   B

Complexity

Conditions 4
Paths 24

Size

Total Lines 27
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

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