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 | 1059 | public function __construct(string $number) |
|
86 | { |
||
87 | 1059 | $this->number = new \ArrayObject(str_split(strrev($number))); |
|
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||
88 | 1059 | $this->multipliers = new \ArrayObject(); |
|
89 | 1059 | $this->replacements = new \ArrayObject(); |
|
90 | |||
91 | 1059 | $this->withMultipliersInterval(2, 9); |
|
92 | 1059 | $this->withModule(static::MODULE_11); |
|
93 | 1059 | $this->multiplySumBy(1); |
|
94 | } |
||
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 | 1059 | public function withMultipliersInterval(int $start, int $end) : DigitCalculator |
|
111 | { |
||
112 | 1059 | $multipliers = []; |
|
113 | 1059 | for ($i = $start; $i <= $end; ++$i) { |
|
114 | 1059 | array_push($multipliers, $i); |
|
115 | } |
||
116 | |||
117 | 1059 | 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 | 1059 | public function withMultipliers(array $multipliers) : DigitCalculator |
|
131 | { |
||
132 | 1059 | $multipliers = array_map(function ($multiplier) { |
|
133 | 1059 | if (!assert(is_int($multiplier))) { |
|
134 | throw new \InvalidArgumentException("The multiplier({$multiplier}) must be integer"); |
||
135 | } |
||
136 | |||
137 | 1059 | return $multiplier; |
|
138 | }, $multipliers); |
||
139 | 1059 | $this->multipliers = new \ArrayObject($multipliers); |
|
140 | |||
141 | 1059 | 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 | 982 | public function useComplementaryInsteadOfModule() : DigitCalculator |
|
153 | { |
||
154 | 982 | $this->additional = true; |
|
155 | |||
156 | 982 | 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 | 1008 | public function replaceWhen(string $replaceTo, ...$integers) : DigitCalculator |
|
170 | { |
||
171 | 1008 | foreach ($integers as $integer) { |
|
172 | 1008 | $this->replacements->offsetSet($integer, $replaceTo); |
|
173 | } |
||
174 | |||
175 | 1008 | 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 | 1059 | public function withModule(int $module) : DigitCalculator |
|
188 | { |
||
189 | 1059 | $this->module = $module; |
|
190 | |||
191 | 1059 | 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() : DigitCalculator |
||
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 | 1059 | public function calculate() : string |
|
215 | { |
||
216 | 1059 | $sum = 0; |
|
217 | 1059 | $position = 0; |
|
218 | 1059 | foreach ($this->number as $digit) { |
|
219 | 1059 | $multiplier = $this->multipliers->offsetGet($position); |
|
220 | 1059 | $total = $digit * $multiplier; |
|
221 | 1059 | $sum += $this->calculateSingleSum($total); |
|
222 | 1059 | $position = $this->nextMultiplier($position); |
|
223 | } |
||
224 | |||
225 | 1059 | $sum = $this->calculateSumMultiplier($sum); |
|
226 | |||
227 | 1059 | $result = $sum % $this->module; |
|
228 | |||
229 | 1059 | $result = $this->calculateAdditionalDigit($result); |
|
230 | |||
231 | 1059 | 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 | 1059 | private function replaceDigit(string $digit) : string |
|
242 | { |
||
243 | 1059 | if ($this->replacements->offsetExists($digit)) { |
|
244 | 230 | return $this->replacements->offsetGet($digit); |
|
245 | } |
||
246 | |||
247 | 904 | 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 | 1059 | private function calculateAdditionalDigit(string $digit) : int |
|
258 | { |
||
259 | 1059 | if ($this->additional) { |
|
260 | 982 | $digit = $this->module - $digit; |
|
261 | } |
||
262 | |||
263 | 1059 | return $digit; |
|
0 ignored issues
–
show
|
|||
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 | 1059 | private function calculateSingleSum(int $total) : int |
|
274 | { |
||
275 | 1059 | if ($this->singleSum) { |
|
276 | return (int) (($total / 10) + ($total % 10)); |
||
277 | } |
||
278 | |||
279 | 1059 | 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 | 1059 | private function nextMultiplier(int $position) : int |
|
290 | { |
||
291 | 1059 | ++$position; |
|
292 | 1059 | if ($position == $this->multipliers->count()) { |
|
293 | 1051 | $position = 0; |
|
294 | } |
||
295 | |||
296 | 1059 | 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 | 145 | public function addDigit(string $digit) : DigitCalculator |
|
307 | { |
||
308 | 145 | $numbers = $this->number->getArrayCopy(); |
|
309 | 145 | array_unshift($numbers, $digit); |
|
310 | 145 | $this->number = new \ArrayObject($numbers); |
|
311 | |||
312 | 145 | 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 | 1059 | public function multiplySumBy(int $multiplier) : DigitCalculator |
|
323 | { |
||
324 | 1059 | $this->sumMultiplier = $multiplier; |
|
325 | |||
326 | 1059 | 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 | 1059 | private function calculateSumMultiplier(int $sum) : int |
|
337 | { |
||
338 | 1059 | return $this->sumMultiplier * $sum; |
|
339 | } |
||
340 | } |
||
341 |