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

Validator::validateLength()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 12
ccs 6
cts 6
cp 1
rs 9.4285
cc 3
eloc 5
nc 3
nop 0
crap 3
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 19
    private function calculateCentury($year) {
194 19
        $currentDate = new \DateTime();
195 19
        $currentYear = $currentDate->format('y');
196 19
        if (!is_null($this->century)) {
197 1
            $century = $this->century;
198 1
        } else {
199 18
            $currentCentury = substr($currentDate->format('Y'), 0, 2);
200 18
            $century = $year < $currentYear ? $currentCentury : $currentCentury - 1;
201
        }   
202
        
203 19
        return $century;
204
    }
205
206
    /**
207
     * Return the validation error
208
     *
209
     * @return string
210
     */
211 11
    public function getError()
212
    {
213 11
        return $this->error;
214
    }
215
216
    /**
217
     * Return true if the provided codice fiscale is valid, false otherwise
218
     *
219
     * @return boolean
220
     */
221 26
    public function isFormallyValid()
222
    {
223 26
        return $this->isValid;
224
    }
225
226
    /**
227
     * Return true if the provided codice fiscale is an omocodia, false otherwise
228
     *
229
     * @return boolean
230
     */
231 1
    public function isOmocodia()
232
    {
233 1
        return $this->foundOmocodiaLevel > 0;
234
    }
235
236
    /**
237
     * Return the provided codice fiscale, cleaned up by omocodia
238
     *
239
     * @return string
240
     */
241 15
    protected function getCodiceFiscaleWithoutOmocodia()
242
    {
243 15
        return $this->codiceFiscaleWithoutOmocodia;
244
    }
245
246
    /**
247
     * Return the birth date
248
     *
249
     * @return \DateTime
250
     */
251 15
    protected function getBirthDate()
252
    {
253 15
        return $this->birthDate;
254
    }
255
256
    /**
257
     * Return the gender
258
     *
259
     * @return string
260
     */
261 15
    protected function getGender()
262
    {
263 15
        return $this->gender;
264
    }
265
}
266