Completed
Pull Request — master (#1596)
by Thomas
35:27
created

DataCollection   B

Complexity

Total Complexity 40

Size/Duplication

Total Lines 440
Duplicated Lines 10.68 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 97.58%

Importance

Changes 0
Metric Value
wmc 40
lcom 1
cbo 5
dl 47
loc 440
ccs 161
cts 165
cp 0.9758
rs 8.2608
c 0
b 0
f 0

19 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
B addPlatformsFile() 0 26 6
B addEnginesFile() 3 22 5
A addDevicesFile() 3 15 4
A addSourceFile() 13 13 1
A addDefaultProperties() 14 14 1
A addDefaultBrowser() 14 14 1
B loadFile() 0 26 5
A getDivisions() 0 6 1
B sortDivisions() 0 27 3
A getDefaultProperties() 0 4 1
A getDefaultBrowser() 0 4 1
A getPlatforms() 0 4 1
A getPlatform() 0 10 2
A getEngines() 0 4 1
A getEngine() 0 10 2
A getDevices() 0 4 1
A getDevice() 0 10 2
A getGenerationDate() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like DataCollection often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DataCollection, and based on these observations, apply Extract Interface, too.

1
<?php
2
declare(strict_types = 1);
3
namespace Browscap\Data;
4
5
use Browscap\Data\Factory\DivisionFactory;
6
use Psr\Log\LoggerInterface;
7
8
/**
9
 * Class DataCollection
10
 */
11
class DataCollection
12
{
13
    /**
14
     * @var \Browscap\Data\Platform[]
15
     */
16
    private $platforms = [];
17
18
    /**
19
     * @var \Browscap\Data\Engine[]
20
     */
21
    private $engines = [];
22
23
    /**
24
     * @var \Browscap\Data\Device[]
25
     */
26
    private $devices = [];
27
28
    /**
29
     * @var \Browscap\Data\Division[]
30
     */
31
    private $divisions = [];
32
33
    /**
34
     * @var \Browscap\Data\Division
35
     */
36
    private $defaultProperties;
37
38
    /**
39
     * @var \Browscap\Data\Division
40
     */
41
    private $defaultBrowser;
42
43
    /**
44
     * @var bool
45
     */
46
    private $divisionsHaveBeenSorted = false;
47
48
    /**
49
     * @var \DateTimeImmutable
50
     */
51
    private $generationDate;
52
53
    /**
54
     * @var \Psr\Log\LoggerInterface
55
     */
56
    private $logger;
57
58
    /**
59
     * @var string[]
60
     */
61
    private $allDivisions = [];
62
63
    /**
64
     * @var DivisionFactory
65
     */
66
    private $divisionFactory;
67
68
    /**
69
     * Create a new data collection for the specified version
70
     *
71
     * @param string                   $version
0 ignored issues
show
Bug introduced by
There is no parameter named $version. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
72
     * @param \Psr\Log\LoggerInterface $logger
73
     */
74
    public function __construct(LoggerInterface $logger)
75
    {
76
        $this->logger          = $logger;
77
        $this->generationDate  = new \DateTimeImmutable();
78
        $this->divisionFactory = new DivisionFactory($logger);
79
    }
80
81
    /**
82
     * Load a platforms.json file and parse it into the platforms data array
83
     *
84
     * @param string $filename Name of the file
85 70
     *
86
     * @throws \RuntimeException         if the file does not exist or has invalid JSON
87 70
     * @throws \UnexpectedValueException
88 70
     *
89 70
     * @return void
90
     */
91
    public function addPlatformsFile(string $filename) : void
92
    {
93
        $json = $this->loadFile($filename);
94
95
        if (!isset($json['platforms'])) {
96 12
            throw new \UnexpectedValueException('required "platforms" structure is missing');
97
        }
98 12
99
        $platformFactory = new Factory\PlatformFactory();
100 12
101
        foreach (array_keys($json['platforms']) as $platformName) {
102
            $platformData = $json['platforms'][$platformName];
103
104
            if (!isset($platformData['match'])) {
105
                throw new \UnexpectedValueException('required attibute "match" is missing');
106 7
            }
107
108 7
            if (!isset($platformData['properties']) && !isset($platformData['inherits'])) {
109
                throw new \UnexpectedValueException('required attibute "properties" is missing');
110
            }
111
112
            $this->platforms[$platformName] = $platformFactory->build($platformData, $json, $platformName);
113
        }
114
115
        $this->divisionsHaveBeenSorted = false;
116
    }
117
118
    /**
119
     * Load a engines.json file and parse it into the platforms data array
120
     *
121 8
     * @param string $filename Name of the file
122
     *
123 8
     * @throws \RuntimeException if the file does not exist or has invalid JSON
124
     *
125 5
     * @return void
126 1
     */
127
    public function addEnginesFile(string $filename) : void
128
    {
129 4
        $json = $this->loadFile($filename);
130
131 4
        if (!isset($json['engines'])) {
132 4
            throw new \UnexpectedValueException('required "engines" structure is missing');
133
        }
134 4
135 1
        $engineFactory = new Factory\EngineFactory();
136
137
        foreach (array_keys($json['engines']) as $engineName) {
138 3
            $engineData = $json['engines'][$engineName];
139 1
140 View Code Duplication
            if (!isset($engineData['properties']) && !isset($engineData['inherits'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
141
                throw new \UnexpectedValueException('required attibute "properties" is missing');
142 2
            }
143
144
            $this->engines[$engineName] = $engineFactory->build($engineData, $json, $engineName);
145 2
        }
146
147 2
        $this->divisionsHaveBeenSorted = false;
148
    }
149
150
    /**
151
     * Load a devices.json file and parse it into the platforms data array
152
     *
153
     * @param string $filename Name of the file
154
     *
155
     * @throws \RuntimeException         if the file does not exist or has invalid JSON
156
     * @throws \UnexpectedValueException if the properties and the inherits kyewords are missing
157
     *
158
     * @return void
159 7
     */
160
    public function addDevicesFile(string $filename) : void
161 7
    {
162
        $json          = $this->loadFile($filename);
163 5
        $deviceFactory = new Factory\DeviceFactory();
164 1
165
        foreach ($json as $deviceName => $deviceData) {
166 View Code Duplication
            if (!isset($deviceData['properties']) && !isset($deviceData['inherits'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
167 4
                throw new \UnexpectedValueException('required attibute "properties" is missing');
168
            }
169 4
170 4
            $this->devices[$deviceName] = $deviceFactory->build($deviceData, $json, $deviceName);
171
        }
172 4
173 1
        $this->divisionsHaveBeenSorted = false;
174
    }
175
176 3
    /**
177
     * Load a JSON file, parse it's JSON and add it to our divisions list
178
     *
179 3
     * @param string $filename Name of the file
180
     *
181 3
     * @throws \RuntimeException         If the file does not exist or has invalid JSON
182
     * @throws \UnexpectedValueException If required attibutes are missing in the division
183
     * @throws \LogicException
184
     *
185
     * @return void
186
     */
187 View Code Duplication
    public function addSourceFile(string $filename) : void
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
188
    {
189
        $divisionData = $this->loadFile($filename);
190
191
        $this->divisions[] = $this->divisionFactory->build(
192
            $divisionData,
193
            $filename,
194 1
            $this->allDivisions,
195
            false
196 1
        );
197 1
198
        $this->divisionsHaveBeenSorted = false;
199 1
    }
200 1
201
    /**
202
     * Load the file for the default properties
203
     *
204 1
     * @param string $filename Name of the file
205
     *
206
     * @throws \RuntimeException if the file does not exist or has invalid JSON
207 1
     *
208
     * @return void
209 1
     */
210 View Code Duplication
    public function addDefaultProperties(string $filename) : void
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
211
    {
212
        $divisionData = $this->loadFile($filename);
213
        $allDivisions = [];
214
215
        $this->defaultProperties = $this->divisionFactory->build(
216
            $divisionData,
217
            $filename,
218
            $allDivisions,
219
            true
220
        );
221
222
        $this->divisionsHaveBeenSorted = false;
223 39
    }
224
225 39
    /**
226
     * Load the file for the default browser
227 37
     *
228 1
     * @param string $filename Name of the file
229
     *
230
     * @throws \RuntimeException if the file does not exist or has invalid JSON
231 36
     *
232 1
     * @return void
233
     */
234 View Code Duplication
    public function addDefaultBrowser(string $filename) : void
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
235 35
    {
236 1
        $divisionData = $this->loadFile($filename);
237
        $allDivisions = [];
238
239 34
        $this->defaultBrowser = $this->divisionFactory->build(
240 1
            $divisionData,
241
            $filename,
242
            $allDivisions,
243 33
            true
244 19
        );
245
246 14
        $this->divisionsHaveBeenSorted = false;
247
    }
248
249 33
    /**
250 33
     * @param string $filename
251 33
     *
252 1
     * @throws \RuntimeException
253
     *
254
     * @return array
255 32
     */
256 1
    private function loadFile(string $filename) : array
257 1
    {
258
        if (!file_exists($filename)) {
259
            throw new \RuntimeException('File "' . $filename . '" does not exist.');
260
        }
261 31
262 30
        if (!is_readable($filename)) {
263
            throw new \RuntimeException('File "' . $filename . '" is not readable.');
264 1
        }
265
266
        $fileContent = file_get_contents($filename);
267 31
268 30
        if (preg_match('/[^ -~\s]/', $fileContent)) {
269 1
            throw new \RuntimeException('File "' . $filename . '" contains Non-ASCII-Characters.');
270
        }
271 1
272 1
        $json = json_decode($fileContent, true);
273 1
274
        if (null === $json) {
275
            throw new \RuntimeException(
276
                'File "' . $filename . '" had invalid JSON. [JSON error: ' . json_last_error_msg() . ']'
277 30
            );
278 1
        }
279 1
280
        return $json;
281
    }
282
283 29
    /**
284 1
     * Get the divisions array containing UA data
285 1
     *
286
     * @return \Browscap\Data\Division[]
287
     */
288
    public function getDivisions() : array
289 28
    {
290 1
        $this->sortDivisions();
291 1
292
        return $this->divisions;
293
    }
294
295 27
    /**
296 1
     * Sort the divisions (if they haven't already been sorted)
297
     *
298 1
     * @return void
299
     */
300
    public function sortDivisions() : void
301
    {
302 26
        if ($this->divisionsHaveBeenSorted) {
303 1
            return;
304 1
        }
305
306
        $sortIndex    = [];
307
        $sortPosition = [];
308 25
309 1
        foreach ($this->divisions as $key => $division) {
310 1
            /* @var \Browscap\Data\Division $division */
311 1
            $sortIndex[$key]    = $division->getSortIndex();
312
            $sortPosition[$key] = $key;
313
        }
314
315 24
        array_multisort(
316
            $sortIndex,
317
            SORT_ASC,
318
            SORT_NUMERIC,
319
            $sortPosition,
320
            SORT_DESC,
321
            SORT_NUMERIC, // if the sortIndex is identical the later added file comes first
322 24
            $this->divisions
323 1
        );
324 1
325
        $this->divisionsHaveBeenSorted = true;
326
    }
327
328 23
    /**
329 1
     * Get the divisions array containing UA data
330 1
     *
331
     * @return \Browscap\Data\Division
332
     */
333
    public function getDefaultProperties() : Division
334 22
    {
335 1
        return $this->defaultProperties;
336 1
    }
337
338
    /**
339
     * Get the divisions array containing UA data
340 21
     *
341 21
     * @return \Browscap\Data\Division
342 21
     */
343 21
    public function getDefaultBrowser() : Division
344
    {
345
        return $this->defaultBrowser;
346 20
    }
347 20
348 20
    /**
349 20
     * Get the array of platform data
350
     *
351
     * @return \Browscap\Data\Platform[]
352 19
     */
353 19
    public function getPlatforms() : array
354 19
    {
355 19
        return $this->platforms;
356
    }
357
358 18
    /**
359 18
     * Get a single platform data array
360 1
     *
361
     * @param string $platform
362 1
     *
363
     * @throws \OutOfBoundsException
364
     * @throws \UnexpectedValueException
365
     *
366 17
     * @return \Browscap\Data\Platform
367 1
     */
368
    public function getPlatform(string $platform) : Platform
369 1
    {
370
        if (!array_key_exists($platform, $this->platforms)) {
371
            throw new \OutOfBoundsException(
372
                'Platform "' . $platform . '" does not exist in data'
373 16
            );
374 1
        }
375
376 1
        return $this->platforms[$platform];
377
    }
378
379
    /**
380 15
     * Get the array of engine data
381 1
     *
382
     * @return \Browscap\Data\Engine[]
383 1
     */
384
    public function getEngines() : array
385
    {
386
        return $this->engines;
387 14
    }
388 1
389 1
    /**
390
     * Get a single engine data array
391 1
     *
392
     * @param string $engine
393 1
     *
394
     * @throws \OutOfBoundsException
395
     * @throws \UnexpectedValueException
396
     *
397 13
     * @return \Browscap\Data\Engine
398 1
     */
399 1
    public function getEngine(string $engine) : Engine
400
    {
401 1
        if (!array_key_exists($engine, $this->engines)) {
402
            throw new \OutOfBoundsException(
403 1
                'Rendering Engine "' . $engine . '" does not exist in data'
404
            );
405
        }
406
407 12
        return $this->engines[$engine];
408 1
    }
409 1
410
    /**
411
     * Get the array of engine data
412
     *
413 11
     * @return \Browscap\Data\Device[]
414 10
     */
415 1
    public function getDevices() : array
416
    {
417 1
        return $this->devices;
418 1
    }
419 1
420
    /**
421
     * Get a single engine data array
422
     *
423 10
     * @param string $device
424 1
     *
425
     * @throws \OutOfBoundsException
426 1
     * @throws \UnexpectedValueException
427 1
     *
428 1
     * @return \Browscap\Data\Device
429
     */
430
    public function getDevice(string $device) : Device
431
    {
432 9
        if (!array_key_exists($device, $this->devices)) {
433 1
            throw new \OutOfBoundsException(
434
                'Device "' . $device . '" does not exist in data'
435 1
            );
436 1
        }
437 1
438
        return $this->devices[$device];
439
    }
440
441 8
    /**
442 5
     * Get the generation DateTime object
443 1
     *
444 1
     * @return \DateTimeImmutable
445
     */
446
    public function getGenerationDate() : \DateTimeImmutable
447
    {
448 4
        return $this->generationDate;
449 1
    }
450
}
451