Passed
Pull Request — master (#32)
by Antonio Oertel
04:01
created

DigitCalculator::nextMultiplier()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 9
ccs 5
cts 5
cp 1
rs 9.6666
cc 2
eloc 5
nc 2
nop 1
crap 2
1
<?php
2
3
namespace Brazanation\Documents;
4
5
/**
6
 * Class DigitCalculator is inspired in DigitoPara class from Java built by Caleum
7
 *
8
 * A fluent interface to calculate digits, used for any Boletos and document numbers.
9
 *
10
 * For example, the digit from 0000039104766 with the multipliers starting from 2 until 7 and using module11,
11
 * follow:
12
 *
13
 * <pre>
14
 *    0  0  0  0  0  3  9  1  0  4  7  6  6 (numeric section)
15
 *    2  7  6  5  4  3  2  7  6  5  4  3  2 (multipliers, from right to left and in cycle)
16
 *    ----------------------------------------- multiplication digit by digit
17
 *     0  0  0  0  0  9 18  7  0 20 28 18 12 -- sum = 112
18
 * </pre>
19
 *
20
 * Gets module from this sum, so, does calculate the additional from module and, if number is 0, 10 or 11,
21
 * the digit result will be 1.
22
 *
23
 * <pre>
24
 *        sum = 112
25
 *        sum % 11 = 2
26
 *        11 - (sum % 11) = 9
27
 * </pre>
28
 *
29
 * @package Brazanation\Documents
30
 *
31
 * @see     https://github.com/caelum/caelum-stella/blob/master/stella-core/src/main/java/br/com/caelum/stella/DigitoPara.java
32
 */
33
class DigitCalculator
34
{
35
    const MODULE_10 = 10;
36
37
    const MODULE_11 = 11;
38
39
    /**
40
     * A list for digits.
41
     *
42
     * @var \ArrayObject
43
     */
44
    protected $number;
45
46
    /**
47
     * A list of integer multipliers.
48
     *
49
     * @var \ArrayObject
50
     */
51
    protected $multipliers;
52
53
    /**
54
     *
55
     * @var bool
56
     */
57
    protected $additional = false;
58
59
    /**
60
     * @var int
61
     */
62
    protected $module = DigitCalculator::MODULE_11;
63
64
    /**
65
     * @var bool
66
     */
67
    protected $singleSum;
68
69
    /**
70
     * @var \ArrayObject
71
     */
72
    private $replacements;
73
74
    /**
75
     * @var int
76
     */
77
    private $sumMultiplier;
78
79
    /**
80
     * Creates object to be filled with fluent interface and store a numeric section into
81
     * a list of digits. It is required because the numeric section could be so bigger than a integer number supports.
82
     *
83
     * @param string $number Base numeric section to be calculate your digit.
84
     */
85 531
    public function __construct($number)
86
    {
87 531
        $this->number = new \ArrayObject(str_split(strrev($number)));
88 531
        $this->multipliers = new \ArrayObject();
89 531
        $this->replacements = new \ArrayObject();
90
91 531
        $this->withMultipliersInterval(2, 9);
92 531
        $this->withModule(static::MODULE_11);
93 531
        $this->multiplySumBy(1);
94 531
    }
95
96
    /**
97
     * Sequential multipliers (or coefficient) and ascending order, this method allow
98
     * to create a list of multipliers.
99
     *
100
     * It will be used in cycle, when the base number is larger than multipliers sequence.
101
     * By default, multipliers are started with 2-9.
102
     *
103
     * You can enter another value and this default will be overwritten.
104
     *
105
     * @param int $start First number of sequential interval of multipliers
106
     * @param int $end   Last number of sequential interval of multipliers
107
     *
108
     * @return DigitCalculator
109
     */
110 531
    public function withMultipliersInterval($start, $end)
111
    {
112 531
        $multipliers = [];
113 531
        for ($i = $start; $i <= $end; ++$i) {
114 531
            array_push($multipliers, $i);
115
        }
116
117 531
        return $this->withMultipliers($multipliers);
118
    }
119
120
    /**
121
     * There are some documents in which the multipliers do not use all the numbers in a range or
122
     * change your order.
123
     *
124
     * In such cases, the multipliers list can be passed through array of integers.
125
     *
126
     * @param int[] $multipliers A list of integers sequence, such as: [9, 8, 7, 6, 5, 4, 3, 2, 1].
127
     *
128
     * @return DigitCalculator
129
     */
130
    public function withMultipliers(array $multipliers)
131
    {
132 531
        $multipliers = array_map(function ($multiplier) {
133 531
            if (!assert(is_int($multiplier))) {
134
                throw new \InvalidArgumentException("The multiplier({$multiplier}) must be integer");
135
            }
136
137 531
            return $multiplier;
138 531
        }, $multipliers);
139 531
        $this->multipliers = new \ArrayObject($multipliers);
140
141 531
        return $this;
142
    }
143
144
    /**
145
     * It is common digit generators need additional module instead of module itself.
146
     *
147
     * So to call this method enables a flag that is used in module method to decide
148
     * if the returned result is pure module or its complementary.
149
     *
150
     * @return DigitCalculator
151
     */
152 491
    public function useComplementaryInsteadOfModule()
153
    {
154 491
        $this->additional = true;
155
156 491
        return $this;
157
    }
158
159
    /**
160
     * There are some documents with specific rules for calculated digits.
161
     *
162
     * Some cases is possible to find X as digit checker.
163
     *
164
     * @param string $replaceTo A string to replace a digit.
165
     * @param int[]  $integers  A list of numbers to be replaced by $replaceTo
166
     *
167
     * @return DigitCalculator
168
     */
169 503
    public function replaceWhen($replaceTo, ...$integers)
170
    {
171 503
        foreach ($integers as $integer) {
172 503
            $this->replacements->offsetSet($integer, $replaceTo);
173
        }
174
175 503
        return $this;
176
    }
177
178
    /**
179
     * Full whereby the rest will be taken and also its complementary.
180
     *
181
     * The default value is DigitCalculator::MODULE_11.
182
     *
183
     * @param int $module A integer to define module (DigitCalculator::MODULE_11 or DigitCalculator::MODULE_10)
184
     *
185
     * @return DigitCalculator
186
     */
187 531
    public function withModule($module)
188
    {
189 531
        $this->module = $module;
190
191 531
        return $this;
192
    }
193
194
    /**
195
     * Indicates whether to calculate the module, the sum of the multiplication results
196
     * should be considered digit by digit.
197
     *
198
     * Eg: 2 * 9 = 18, sum = 9 (1 + 8) instead of 18
199
     *
200
     * @return DigitCalculator
201
     */
202
    public function singleSum()
203
    {
204
        $this->singleSum = true;
205
206
        return $this;
207
    }
208
209
    /**
210
     * Calculates the check digit from given numeric section.
211
     *
212
     * @return string Returns a single calculated digit.
213
     */
214 531
    public function calculate()
215
    {
216 531
        $sum = 0;
217 531
        $position = 0;
218 531
        foreach ($this->number as $digit) {
219 531
            $multiplier = $this->multipliers->offsetGet($position);
220 531
            $total = $digit * $multiplier;
221 531
            $sum += $this->calculateSingleSum($total);
222 531
            $position = $this->nextMultiplier($position);
223
        }
224
225 531
        $sum = $this->calculateSumMultiplier($sum);
226
227 531
        $result = $sum % $this->module;
228
229 531
        $result = $this->calculateAdditionalDigit($result);
230
231 531
        return $this->replaceDigit($result);
232
    }
233
234
    /**
235
     * Replaces the digit when mapped to be replaced by other digit.
236
     *
237
     * @param string $digit A digit to be replaced.
238
     *
239
     * @return string Returns digit replaced if it has been mapped, otherwise returns given digit.
240
     */
241 531
    private function replaceDigit($digit)
242
    {
243 531
        if ($this->replacements->offsetExists($digit)) {
244 115
            return $this->replacements->offsetGet($digit);
245
        }
246
247 453
        return $digit;
248
    }
249
250
    /**
251
     * Calculates additional digit when is additional is defined.
252
     *
253
     * @param string $digit A digit to be subtract from module.
254
     *
255
     * @return int Returns calculated digit.
256
     */
257 531
    private function calculateAdditionalDigit($digit)
258
    {
259 531
        if ($this->additional) {
260 491
            $digit = $this->module - $digit;
261
        }
262
263 531
        return $digit;
264
    }
265
266
    /**
267
     * Calculates single sum.
268
     *
269
     * @param int $total A total to be calculated.
270
     *
271
     * @return int Returns a calculated total.
272
     */
273 531
    private function calculateSingleSum($total)
274
    {
275 531
        if ($this->singleSum) {
276
            return intval(($total / 10) + ($total % 10));
277
        }
278
279 531
        return $total;
280
    }
281
282
    /**
283
     * Gets the next multiplier.
284
     *
285
     * @param int $position Current position.
286
     *
287
     * @return int Returns next position or zero (0) when it is greater than number of defined multipliers.
288
     */
289 531
    private function nextMultiplier($position)
290
    {
291 531
        ++$position;
292 531
        if ($position == $this->multipliers->count()) {
293 527
            $position = 0;
294
        }
295
296 531
        return $position;
297
    }
298
299
    /**
300
     * Adds a digit into number collection.
301
     *
302
     * @param string $digit Digit to be prepended into number collection.
303
     *
304
     * @return DigitCalculator
305
     */
306 68
    public function addDigit($digit)
307
    {
308 68
        $numbers = $this->number->getArrayCopy();
309 68
        array_unshift($numbers, $digit);
310 68
        $this->number = new \ArrayObject($numbers);
311
312 68
        return $this;
313
    }
314
315
    /**
316
     * Defines the multiplier factor after calculate the sum of digits.
317
     *
318
     * @param int $multiplier A integer to multiply the sum result.
319
     *
320
     * @return DigitCalculator
321
     */
322 531
    public function multiplySumBy($multiplier)
323
    {
324 531
        $this->sumMultiplier = $multiplier;
325
326 531
        return $this;
327
    }
328
329
    /**
330
     * Multiplies the sum result with defined multiplier factor.
331
     *
332
     * @param int $sum The result of calculation from digits.
333
     *
334
     * @return int
335
     */
336 531
    private function calculateSumMultiplier($sum)
337
    {
338 531
        return $this->sumMultiplier * $sum;
339
    }
340
}
341