Completed
Push — master ( 5ad332...7a2bea )
by Antonio Oertel
23:48 queued 19:23
created

DigitCalculator::calculateSingleSum()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.0625

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 3
cts 4
cp 0.75
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 1
crap 2.0625
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
     * Creates object to be filled with fluent interface and store a numeric section into
76
     * a list of digits. It is required because the numeric section could be so bigger than a integer number supports.
77
     *
78
     * @param string $number Base numeric section to be calculate your digit.
79
     */
80 15
    public function __construct($number)
81
    {
82 15
        $this->number = new \ArrayObject(str_split(strrev($number)));
83 15
        $this->multipliers = new \ArrayObject();
84 15
        $this->replacements = new \ArrayObject();
85
86 15
        $this->withMultipliersInterval(2, 9);
87 15
        $this->withModule(static::MODULE_11);
88 15
    }
89
90
    /**
91
     * Sequential multipliers (or coefficient) and ascending order, this method allow
92
     * to create a list of multipliers.
93
     *
94
     * It will be used in cycle, when the base number is larger than multipliers sequence.
95
     * By default, multipliers are started with 2-9.
96
     *
97
     * You can enter another value and this default will be overwritten.
98
     *
99
     * @param int $start First number of sequential interval of multipliers
100
     * @param int $end   Last number of sequential interval of multipliers
101
     *
102
     * @return DigitCalculator
103
     */
104 15
    public function withMultipliersInterval($start, $end)
105
    {
106 15
        $multipliers = [];
107 15
        for ($i = $start; $i <= $end; ++$i) {
108 15
            array_push($multipliers, $i);
109
        }
110
111 15
        return $this->withMultipliers($multipliers);
112
    }
113
114
    /**
115
     * There are some documents in which the multipliers do not use all the numbers in a range or
116
     * change your order.
117
     *
118
     * In such cases, the multipliers list can be passed through array of integers.
119
     *
120
     * @param int[] $multipliers A list of integers sequence, such as: [9, 8, 7, 6, 5, 4, 3, 2, 1].
121
     *
122
     * @return DigitCalculator
123
     */
124
    public function withMultipliers(array $multipliers)
125
    {
126 15
        $multipliers = array_map(function ($multiplier) {
127 15
            if (!assert(is_int($multiplier))) {
128
                throw new \InvalidArgumentException("The multiplier({$multiplier}) must be integer");
129
            }
130
131 15
            return $multiplier;
132 15
        }, $multipliers);
133 15
        $this->multipliers = new \ArrayObject($multipliers);
134
135 15
        return $this;
136
    }
137
138
    /**
139
     * It is common digit generators need additional module instead of module itself.
140
     *
141
     * So to call this method enables a flag that is used in module method to decide
142
     * if the returned result is pure module or its complementary.
143
     *
144
     * @return DigitCalculator
145
     */
146 8
    public function useComplementaryInsteadOfModule()
147
    {
148 8
        $this->additional = true;
149
150 8
        return $this;
151
    }
152
153
    /**
154
     * There are some documents with specific rules for calculated digits.
155
     *
156
     * Some cases is possible to find X as digit checker.
157
     *
158
     * @param string $replaceTo A string to replace a digit.
159
     * @param int[]  $integers  A list of numbers to be replaced by $replaceTo
160
     *
161
     * @return DigitCalculator
162
     */
163 10
    public function replaceWhen($replaceTo, ...$integers)
164
    {
165 10
        foreach ($integers as $integer) {
166 10
            $this->replacements->offsetSet($integer, $replaceTo);
167
        }
168
169 10
        return $this;
170
    }
171
172
    /**
173
     * Full whereby the rest will be taken and also its complementary.
174
     *
175
     * The default value is DigitCalculator::MODULE_11.
176
     *
177
     * @param int $module A integer to define module (DigitCalculator::MODULE_11 or DigitCalculator::MODULE_10)
178
     *
179
     * @return DigitCalculator
180
     */
181 15
    public function withModule($module)
182
    {
183 15
        $this->module = $module;
184
185 15
        return $this;
186
    }
187
188
    /**
189
     * Indicates whether to calculate the module, the sum of the multiplication results
190
     * should be considered digit by digit.
191
     *
192
     * Eg: 2 * 9 = 18, sum = 9 (1 + 8) instead of 18
193
     *
194
     * @return DigitCalculator
195
     */
196
    public function singleSum()
197
    {
198
        $this->singleSum = true;
199
200
        return $this;
201
    }
202
203
    /**
204
     * Calculates the check digit from given numeric section.
205
     *
206
     * @return string Returns a single calculated digit.
207
     */
208 15
    public function calculate()
209
    {
210 15
        $sum = 0;
211 15
        $position = 0;
212 15
        foreach ($this->number as $digit) {
213 15
            $multiplier = $this->multipliers->offsetGet($position);
214 15
            $total = $digit * $multiplier;
215 15
            $sum += $this->calculateSingleSum($total);
216 15
            $position = $this->nextMultiplier($position);
217
        }
218
219 15
        $result = $sum % $this->module;
220
221 15
        $result = $this->calculateAdditionalDigit($result);
222
223 15
        return $this->replaceDigit($result);
224
    }
225
226
    /**
227
     * Replaces the digit when mapped to be replaced by other digit.
228
     *
229
     * @param string $digit A digit to be replaced.
230
     *
231
     * @return string Returns digit replaced if it has been mapped, otherwise returns given digit.
232
     */
233 15
    private function replaceDigit($digit)
234
    {
235 15
        if ($this->replacements->offsetExists($digit)) {
236 2
            return $this->replacements->offsetGet($digit);
237
        }
238
239 14
        return $digit;
240
    }
241
242
    /**
243
     * Calculates additional digit when is additional is defined.
244
     *
245
     * @param string $digit A digit to be subtract from module.
246
     *
247
     * @return int Returns calculated digit.
248
     */
249 15
    private function calculateAdditionalDigit($digit)
250
    {
251 15
        if ($this->additional) {
252 8
            $digit = $this->module - $digit;
253
        }
254
255 15
        return $digit;
256
    }
257
258
    /**
259
     * Calculates single sum.
260
     *
261
     * @param int $total A total to be calculated.
262
     *
263
     * @return float Returns a calculated total.
264
     */
265 15
    private function calculateSingleSum($total)
266
    {
267 15
        if ($this->singleSum) {
268
            return ($total / 10) + ($total % 10);
269
        }
270
271 15
        return $total;
272
    }
273
274
    /**
275
     * Gets the next multiplier.
276
     *
277
     * @param int $position Current position.
278
     *
279
     * @return int Returns next position or zero (0) when it is greater than number of defined multipliers.
280
     */
281 15
    private function nextMultiplier($position)
282
    {
283 15
        ++$position;
284 15
        if ($position == $this->multipliers->count()) {
285 15
            $position = 0;
286
        }
287
288 15
        return $position;
289
    }
290
291
    /**
292
     * Adds a digit into number collection.
293
     *
294
     * @param string $digit Digit to be prepended into number collection.
295
     *
296
     * @return DigitCalculator
297
     */
298
    public function addDigit($digit)
299
    {
300
        $numbers = $this->number->getArrayCopy();
301
        array_unshift($numbers, $digit);
302
        $this->number = new \ArrayObject($numbers);
303
304
        return $this;
305
    }
306
}
307