Completed
Push — develop ( b270a8...088d24 )
by Alexandru
01:30
created

Cnp::setBirthday()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 16
ccs 10
cts 10
cp 1
rs 9.4285
cc 1
eloc 9
nc 1
nop 1
crap 1
1
<?php
2
/**
3
 * Copyright 2017 Alexandru Guzinschi <[email protected]>
4
 *
5
 * This software may be modified and distributed under the terms
6
 * of the MIT license. See the LICENSE file for details.
7
 */
8
namespace Vimishor\Cnp;
9
10
use Gentle\Embeddable\Embeddable;
11
use Gentle\Embeddable\Date;
12
use Vimishor\Cnp\Exception\InvalidCnpException;
13
14
/**
15
 * @author Alexandru Guzinschi <[email protected]>
16
 */
17
final class Cnp extends Embeddable
18
{
19
    /** @var Gender */
20
    private $gender;
21
22
    /** @var Date */
23
    private $birthday;
24
25
    /** @var County */
26
    private $county;
27
28
    /** @var Serial */
29
    private $serial;
30
31
    /** @var Checksum */
32
    private $checksum;
33
34
    /**
35
     * Build a {@link CNP} instance from a CNP number.
36
     *
37
     * @static
38
     * @access public
39
     * @param  string $number A CNP number.
40
     * @return self
41
     *
42
     * @throws InvalidCnpException
43
     */
44 20
    public static function fromString($number)
45
    {
46 20
        if (13 !== mb_strlen($number)) {
47 4
            throw new InvalidCnpException(
48 4
                'Invalid CNP length.',
49 4
                0,
50 4
                new \LengthException('Expected a string number of 13 characters.')
51 2
            );
52
        }
53
54
        try {
55 16
            return new self(
56 16
                new Gender(self::getValue($number, 0)),
57 14
                new Date(
58 14
                    new Date\Year('19'.self::getValue($number, 1).self::getValue($number, 2)),
59 14
                    new Date\Month(self::getValue($number, 3).self::getValue($number, 4)),
60 14
                    new Date\Day(self::getValue($number, 5).self::getValue($number, 6))
61 6
                ),
62 10
                new County(self::getValue($number, 7).self::getValue($number, 8)),
63 8
                new Serial(self::getValue($number, 9).self::getValue($number, 10).self::getValue($number, 11)),
64 6
                new Checksum(self::getValue($number, 12))
65 3
            );
66 16
        } catch (\Exception $e) {
67 16
            throw new InvalidCnpException($e->getMessage(), $e->getCode(), $e);
68
        }
69
    }
70
71
    /**
72
     * @param Gender   $gender
73
     * @param Date     $birthday
74
     * @param County   $county
75
     * @param Serial   $serial
76
     * @param Checksum $checksum
77
     *
78
     * @throws InvalidCnpException
79
     */
80 38
    public function __construct(Gender $gender, Date $birthday, County $county, Serial $serial, Checksum $checksum)
81
    {
82 38
        $this->gender = $gender;
83 38
        $this->serial = $serial;
84 38
        $this->checksum = $checksum;
85
86
        try {
87 38
            $this->setBirthday($birthday);
88 38
            $this->setCounty($county);
89 21
        } catch (\DomainException $e) {
90 4
            throw new InvalidCnpException('Provided number is not a valid CNP.', $e->getCode(), $e);
91
        }
92
93 34
        $this->validate((string)$this);
94 32
    }
95
96
    /**
97
     * @access public
98
     * @return Gender
99
     */
100 38
    public function getGender()
101
    {
102 38
        return $this->gender;
103
    }
104
105
    /**
106
     * @access public
107
     * @return Date
108
     */
109 38
    public function getBirthday()
110
    {
111 38
        return $this->birthday;
112
    }
113
114
    /**
115
     * @access public
116
     * @return County
117
     */
118 34
    public function getCounty()
119
    {
120 34
        return $this->county;
121
    }
122
123
    /**
124
     * @access public
125
     * @return Serial
126
     */
127 34
    public function getSerial()
128
    {
129 34
        return $this->serial;
130
    }
131
132
    /**
133
     * @access public
134
     * @return Checksum
135
     */
136 34
    public function getChecksum()
137
    {
138 34
        return $this->checksum;
139
    }
140
141
    /**
142
     * @access public
143
     * @return bool
144
     */
145 4
    public function isResident()
146
    {
147 4
        return in_array(sprintf('%s', $this->getGender()), ['7', '8'], false);
148
    }
149
150
    /**
151
     * {@inheritDoc}
152
     */
153 16
    public function equals(Embeddable $object)
154
    {
155 16
        return get_class($object) === 'Vimishor\Cnp\Cnp' && (string)$this === (string)$object;
156
    }
157
158
    /**
159
     * {@inheritDoc}
160
     */
161 34
    public function __toString()
162
    {
163 34
        return (string)$this->getGender().
164 34
            (string)$this->getBirthday()->asDateTime()->format('ymd').
165 34
            (string)$this->getCounty().
166 34
            (string)$this->getSerial().
167 34
            (string)$this->getChecksum();
168
    }
169
170
    /**
171
     * Rebuild a date object with the year changed.
172
     *
173
     * Year prefix is changed according to gender.
174
     *
175
     * @access private
176
     * @param  Date $birthday
177
     * @return $this
178
     */
179 38
    private function setBirthday(Date $birthday)
180
    {
181 38
        $year   = (string)$birthday->getYear();
182 38
        $prefix = $this->getYearPrefix($this->getGender());
183
184
        /** @noinspection ExceptionsAnnotatingAndHandlingInspection */
185 38
        $date = new Date(
186 38
            new Date\Year($prefix . self::getValue($year, 2) . self::getValue($year, 3)),
187 38
            new Date\Month((string)$birthday->getMonth()),
188 38
            new Date\Day((string)$birthday->getDay())
189 19
        );
190
191 38
        $this->birthday = $date;
192
193 38
        return $this;
194
    }
195
196
    /**
197
     * @access private
198
     * @param  County $county
199
     * @return $this
200
     *
201
     * @throws \DomainException
202
     */
203 38
    private function setCounty(County $county)
204
    {
205
        // Allow usage of Bucharest's district 7 and 8 only for dates prior to December 19, 1979.
206 38
        if (in_array((string)$county, ['47', '48'], false) &&
207 27
            strtotime($this->getBirthday()) > strtotime(Date::fromString('1979-12-19T23:59:23+00:00'))
208 19
        ) {
209 4
            $district = (string)$county;
210 4
            throw new \DomainException(
211 4
                sprintf("Bucharest's district %d doesn't exist anymore since 1979.", $district[1])
212 2
            );
213
        }
214
215 34
        $this->county = $county;
216
217 34
        return $this;
218
    }
219
220
    /**
221
     * Extract element from string at given position.
222
     *
223
     * @access private
224
     * @param  string $from
225
     * @param  int    $position
226
     * @return string
227
     */
228 48
    private static function getValue($from, $position)
229
    {
230 48
        $arr = str_split($from);
231
232 48
        return (string)$arr[$position];
233
    }
234
235
    /**
236
     * @access private
237
     * @param  Gender $gender
238
     * @return int
239
     */
240 38
    private function getYearPrefix(Gender $gender)
241
    {
242 38
        $prefix = 0;
243
244 38
        switch ((int)sprintf('%s', $gender)) {
245 38
            case 1:
246 34
            case 2:
247 31
            case 7:
248 28
            case 8:
249 24
                $prefix = 19;
250 24
                break;
251 14
            case 3:
252 14
            case 4:
253 4
                $prefix = 18;
254 4
                break;
255 10
            case 5:
256 5
            case 6:
257 10
                $prefix = 20;
258 10
                break;
259
        } // @codeCoverageIgnore
260
261 38
        return $prefix;
262
    }
263
264
    /**
265
     * Calculate checksum and compare with the one provided in order to validate
266
     *
267
     * @access private
268
     * @param  string $cnp
269
     * @return void
270
     *
271
     * @throws \Vimishor\Cnp\Exception\InvalidCnpException
272
     */
273 34
    private function validate($cnp)
274
    {
275
        // calculate control number and compare in order to validate
276 34
        if ((int)sprintf('%s', $this->getChecksum()) !== (int)$this->calculateChecksum($cnp)) {
277 2
            throw new InvalidCnpException('Provided number is not a valid CNP.');
278
        }
279 32
    }
280
281
    /**
282
     * @access private
283
     * @param  string $cnp
284
     * @return int
285
     */
286 34
    private function calculateChecksum($cnp)
287
    {
288 34
        $sum    = 0;
289
290 34
        for ($i = 0; $i <= 11; ++$i) {
291 34
            $sum += (int) self::getValue((string) $cnp, $i) * (int) self::getValue('279146358279', $i);
292 17
        }
293
294 34
        $sum %= 11;
295
296
        // Rule says that 10 should be interpreted as 1
297 34
        return (10 === $sum) ? 1 : $sum;
298
    }
299
}
300