Parser   A
last analyzed

Complexity

Total Complexity 25

Size/Duplication

Total Lines 253
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 25
lcom 1
cbo 3
dl 0
loc 253
rs 10
c 0
b 0
f 0
ccs 72
cts 72
cp 1

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A fetchFNC1Prefix() 0 10 3
A fetchFixedAI() 0 16 3
A fetchKnownAI() 0 11 3
C parse() 0 81 13
A assertValueIsValid() 0 8 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Lamoda\GS1Parser\Parser;
6
7
use Lamoda\GS1Parser\Barcode;
8
use Lamoda\GS1Parser\Exception\InvalidBarcodeException;
9
10
/**
11
 * Performs barcode parsing according to
12
 * https://www.gs1.org/sites/default/files/docs/barcodes/GS1_General_Specifications.pdf
13
 */
14
final class Parser implements ParserInterface
15
{
16
    private const ENCODABLE_VALUE_CHARACTERS_SET = [
17
        '!',
18
        '"',
19
        '%',
20
        '&',
21
        '\'',
22
        '(',
23
        ')',
24
        '*',
25
        '+',
26
        ',',
27
        '-',
28
        '_',
29
        '.',
30
        '/',
31
        '0',
32
        '1',
33
        '2',
34
        '3',
35
        '4',
36
        '5',
37
        '6',
38
        '7',
39
        '8',
40
        '9',
41
        ':',
42
        ';',
43
        '<',
44
        '=',
45
        '>',
46
        '?',
47
        'A',
48
        'B',
49
        'C',
50
        'D',
51
        'E',
52
        'F',
53
        'G',
54
        'H',
55
        'I',
56
        'J',
57
        'K',
58
        'L',
59
        'M',
60
        'N',
61
        'O',
62
        'P',
63
        'Q',
64
        'R',
65
        'S',
66
        'T',
67
        'U',
68
        'V',
69
        'W',
70
        'X',
71
        'Y',
72
        'Z',
73
        'a',
74
        'b',
75
        'c',
76
        'd',
77
        'e',
78
        'f',
79
        'g',
80
        'h',
81
        'i',
82
        'j',
83
        'k',
84
        'l',
85
        'm',
86
        'n',
87
        'o',
88
        'p',
89
        'q',
90
        'r',
91
        's',
92
        't',
93
        'u',
94
        'v',
95
        'w',
96
        'x',
97
        'y',
98
        'z',
99
    ];
100
    private const FIXED_LENGTH_AIS = [
101
        '00' => 20,
102
        '01' => 16,
103
        '02' => 16,
104
        '03' => 16,
105
        '04' => 18,
106
        '11' => 8,
107
        '12' => 8,
108
        '13' => 8,
109
        '14' => 8,
110
        '15' => 8,
111
        '16' => 8,
112
        '17' => 8,
113
        '18' => 8,
114
        '19' => 8,
115
        '20' => 4,
116
        '31' => 10,
117
        '32' => 10,
118
        '33' => 10,
119
        '34' => 10,
120
        '35' => 10,
121
        '36' => 10,
122
        '41' => 16,
123
    ];
124
    private const FIXED_AI_LENGTH = 2;
125
126
    /**
127
     * @var ParserConfig
128
     */
129
    private $config;
130
131 18
    public function __construct(ParserConfig $config)
132
    {
133 18
        $this->config = $config;
134 18
    }
135
136 18
    public function parse(string $data): Barcode
137
    {
138 18
        $data = trim($data);
139
140 18
        if ($data === '') {
141 1
            throw InvalidBarcodeException::becauseBarcodeIsEmpty();
142
        }
143
144 17
        [$fnc1Prefix, $codeType] = $this->fetchFNC1Prefix($data);
145
146 17
        if ($fnc1Prefix === null && $this->config->isFnc1SequenceRequired()) {
147 1
            throw InvalidBarcodeException::becauseFNC1SequenceIsNotFound();
148
        }
149
150 16
        $codeOffset = strlen((string)$fnc1Prefix);
151 16
        $dataLength = strlen($data);
152
153 16
        if ($dataLength <= $codeOffset) {
154 1
            throw InvalidBarcodeException::becauseNoDataPresent();
155
        }
156
157 15
        $position = $codeOffset;
158 15
        $foundAIs = [];
159 15
        $buffer = [];
160 15
        while ($position < $dataLength) {
161 15
            [$ai, $length] = $this->fetchFixedAI($data, $position, $dataLength);
162 15
            $value = null;
0 ignored issues
show
Unused Code introduced by
$value is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
163
164 15
            if ($ai !== null) {
165 14
                if ($position + $length > $dataLength) {
166 1
                    throw InvalidBarcodeException::becauseNotEnoughDataFoAI(
167 1
                        $ai,
168 1
                        $length,
169 1
                        $dataLength - $position
170
                    );
171
                }
172
173 13
                $isKnownAI = in_array($ai, $this->config->getKnownAIs(), true);
174
175 13
                if ($isKnownAI) {
176 3
                    $value = substr($data, $position + self::FIXED_AI_LENGTH, $length - self::FIXED_AI_LENGTH);
177
                } else {
178 10
                    $ai = null;
179 10
                    $value = substr($data, $position, $length);
180
                }
181
182 13
                if (strpos($value, $this->config->getGroupSeparator()) !== false) {
183 1
                    throw InvalidBarcodeException::becauseGroupSeparatorWasNotExpected($value);
184
                }
185
186 12
                $position += $length;
187
            } else {
188 12
                [$ai, $aiLength] = $this->fetchKnownAI($data, $position);
189
190 12
                $groupSeparatorPosition = strpos($data, $this->config->getGroupSeparator(), $position);
191 12
                if ($groupSeparatorPosition !== false) {
192 6
                    $length = $groupSeparatorPosition - $position;
193
                } else {
194 9
                    $length = $dataLength - $position;
195
                }
196
197 12
                if ($ai) {
198 3
                    $value = substr($data, $position + $aiLength, $length - $aiLength);
199
                } else {
200 9
                    $value = substr($data, $position, $length);
201
                }
202
203 12
                $position += $length + strlen($this->config->getGroupSeparator());
204
            }
205
206 13
            $this->assertValueIsValid($value);
207
208 12
            if ($ai) {
209 3
                $foundAIs[$ai] = $value;
210
            } else {
211 9
                $buffer[] = $value;
212
            }
213
        }
214
215 12
        return new Barcode($data, $codeType, $foundAIs, $buffer, (string)$fnc1Prefix);
216
    }
217
218 17
    private function fetchFNC1Prefix(string $data): array
219
    {
220 17
        foreach ($this->config->getFnc1PrefixMap() as $prefix => $codeType) {
221 17
            if (substr_compare($data, $prefix, 0, strlen($prefix), true) === 0) {
222 17
                return [$prefix, $codeType];
223
            }
224
        }
225
226 5
        return [null, Barcode::TYPE_UNKNOWN];
227
    }
228
229 15
    private function fetchFixedAI(string $data, int $position, int $dataLength): array
230
    {
231 15
        if ($dataLength - $position < self::FIXED_AI_LENGTH) {
232 1
            return [null, null];
233
        }
234
235 14
        $ai = substr($data, $position, self::FIXED_AI_LENGTH);
236
237 14
        $length = self::FIXED_LENGTH_AIS[$ai] ?? null;
238
239 14
        if ($length === null) {
240 11
            return [null, null];
241
        }
242
243 14
        return [$ai, $length];
244
    }
245
246 12
    private function fetchKnownAI(string $data, int $position): array
247
    {
248 12
        foreach ($this->config->getKnownAIs() as $ai) {
249 3
            $aiLength = strlen($ai);
250 3
            if (substr_compare($data, $ai, $position, $aiLength, true) === 0) {
251 3
                return [substr($data, $position, $aiLength), $aiLength];
252
            }
253
        }
254
255 9
        return [null, null];
256
    }
257
258 13
    private function assertValueIsValid(string $value): void
259
    {
260 13
        $unencodableCharacters = array_diff(str_split($value), self::ENCODABLE_VALUE_CHARACTERS_SET);
261
262 13
        if (count($unencodableCharacters) > 0) {
263 1
            throw InvalidBarcodeException::becauseValueContainsInvalidCharacters(...$unencodableCharacters);
264
        }
265 12
    }
266
}
267