Completed
Pull Request — master (#1596)
by Thomas
03:09
created

Expander::parseUserAgent()   C

Complexity

Conditions 11
Paths 90

Size

Total Lines 85
Code Lines 56

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 38
CRAP Score 13.0039

Importance

Changes 0
Metric Value
dl 0
loc 85
ccs 38
cts 51
cp 0.7451
rs 5.2653
c 0
b 0
f 0
cc 11
eloc 56
nc 90
nop 5
crap 13.0039

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * This file is part of the browscap package.
4
 *
5
 * Copyright (c) 1998-2017, Browser Capabilities Project
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
declare(strict_types = 1);
12
namespace Browscap\Data;
13
14
use Browscap\Data\Helper\CheckDeviceData;
15
use Browscap\Data\Helper\CheckEngineData;
16
use Browscap\Data\Helper\CheckPlatformData;
17
use Browscap\Data\Helper\TrimProperty;
18
use Psr\Log\LoggerInterface;
19
20
/**
21
 * Class Expander
22
 *
23
 * @category   Browscap
24
 *
25
 * @author     Thomas Müller <[email protected]>
26
 */
27
class Expander
28
{
29
    /**
30
     * @var \Browscap\Data\DataCollection
31
     */
32
    private $collection;
33
34
    /**
35
     * @var \Psr\Log\LoggerInterface
36
     */
37
    private $logger;
38
39
    /**
40
     * This store the components of the pattern id that are later merged into a string. Format for this
41
     * can be seen in the {@see resetPatternId} method.
42
     *
43
     * @var array
44
     */
45
    private $patternId = [];
46
47
    /**
48
     * @var \Browscap\Data\Helper\CheckDeviceData
49
     */
50
    private $checkDeviceData;
51
52
    /**
53
     * @var \Browscap\Data\Helper\CheckEngineData
54
     */
55
    private $checkEngineData;
56
57
    /**
58
     * @var \Browscap\Data\Helper\CheckPlatformData
59
     */
60
    private $checkPlatformData;
61
62
    /**
63
     * @var \Browscap\Data\Helper\TrimProperty
64
     */
65
    private $trimProperty;
66
67
    /**
68
     * Create a new data expander
69
     *
70
     * @param \Psr\Log\LoggerInterface      $logger
71
     * @param \Browscap\Data\DataCollection $collection
72
     */
73 13
    public function __construct(LoggerInterface $logger, DataCollection $collection)
74
    {
75 13
        $this->logger            = $logger;
76 13
        $this->collection        = $collection;
77 13
        $this->checkDeviceData   = new CheckDeviceData();
78 13
        $this->checkEngineData   = new CheckEngineData();
79 13
        $this->checkPlatformData = new CheckPlatformData();
80 13
        $this->trimProperty      = new TrimProperty();
81 13
    }
82
83
    /**
84
     * @param \Browscap\Data\Division $division
85
     * @param string                  $divisionName
86
     *
87
     * @throws \UnexpectedValueException
88
     *
89
     * @return array
90
     */
91 13
    public function expand(Division $division, string $divisionName) : array
92
    {
93 13
        $allInputDivisions = $this->parseDivision(
94 13
            $division,
95 13
            $divisionName
96
        );
97
98 13
        return $this->expandProperties($allInputDivisions);
99
    }
100
101
    /**
102
     * Resets the pattern id
103
     *
104
     * @return void
105
     */
106 12
    private function resetPatternId() : void
107
    {
108 12
        $this->patternId = [
109
            'division' => '',
110
            'useragent' => '',
111
            'platform' => '',
112
            'device' => '',
113
            'child' => '',
114
        ];
115 12
    }
116
117
    /**
118
     * Render a single division
119
     *
120
     * @param \Browscap\Data\Division $division
121
     * @param string                  $divisionName
122
     *
123
     * @return array
124
     */
125 13
    private function parseDivision(Division $division, string $divisionName) : array
126
    {
127 13
        $output = [];
128
129 13
        $i = 0;
130 13
        foreach ($division->getUserAgents() as $uaData) {
131 12
            $this->resetPatternId();
132 12
            $this->patternId['division']  = $division->getFileName();
133 12
            $this->patternId['useragent'] = $i;
134
135 12
            $output = array_merge(
136 12
                $output,
137 12
                $this->parseUserAgent(
138 12
                    $uaData,
139 12
                    $division->isLite(),
140 12
                    $division->isStandard(),
141 12
                    $division->getSortIndex(),
142 12
                    $divisionName
143
                )
144
            );
145 12
            ++$i;
146
        }
147
148 13
        return $output;
149
    }
150
151
    /**
152
     * Render a single User Agent block
153
     *
154
     * @param \Browscap\Data\Useragent $uaData
155
     * @param bool                     $lite
156
     * @param bool                     $standard
157
     * @param int                      $sortIndex
158
     * @param string                   $divisionName
159
     *
160
     * @return array
161
     */
162 12
    private function parseUserAgent(Useragent $uaData, bool $lite, bool $standard, int $sortIndex, string $divisionName) : array
163
    {
164 12
        $uaProperties = $uaData->getProperties();
165
166 12
        if (null !== $uaData->getPlatform()) {
167
            $this->patternId['platform'] = $uaData->getPlatform();
168
            $platform                    = $this->collection->getPlatform($uaData->getPlatform());
169
170
            if (!$platform->isLite()) {
171
                $lite = false;
172
            }
173
174
            if (!$platform->isStandard()) {
175
                $standard = false;
176
            }
177
178
            $platformProperties = $platform->getProperties();
179
        } else {
180 12
            $this->patternId['platform']       = '';
181 12
            $platformProperties                = [];
182
        }
183
184 12
        if (null !== $uaData->getEngine()) {
185
            $engine           = $this->collection->getEngine($uaData->getEngine());
186
            $engineProperties = $engine->getProperties();
187
        } else {
188 12
            $engineProperties = [];
189
        }
190
191 12
        if (null !== $uaData->getDevice()) {
192
            $device           = $this->collection->getDevice($uaData->getDevice());
193
            $deviceProperties = $device->getProperties();
194
195
            if (!$device->isStandard()) {
196
                $standard = false;
197
            }
198
        } else {
199 12
            $deviceProperties = [];
200
        }
201
202 12
        $ua = $uaData->getUserAgent();
203
204
        $output = [
205 12
            $ua => array_merge(
206
                [
207 12
                    'lite' => $lite,
208 12
                    'standard' => $standard,
209 12
                    'sortIndex' => $sortIndex,
210 12
                    'division' => $divisionName,
211
                ],
212 12
                $platformProperties,
213 12
                $engineProperties,
214 12
                $deviceProperties,
215 12
                $uaProperties
216
            ),
217
        ];
218
219 12
        $i = 0;
220 12
        foreach ($uaData->getChildren() as $child) {
221 6
            $this->patternId['child'] = $i;
222 6
            if (isset($child['devices']) && is_array($child['devices'])) {
223
                // Replace our device array with a single device property with our #DEVICE# token replaced
224 3
                foreach ($child['devices'] as $deviceMatch => $deviceName) {
225 3
                    $this->patternId['device'] = $deviceMatch;
226 3
                    $subChild                  = $child;
227 3
                    $subChild['match']         = str_replace('#DEVICE#', $deviceMatch, $subChild['match']);
228 3
                    $subChild['device']        = $deviceName;
229 3
                    unset($subChild['devices']);
230 3
                    $output = array_merge(
231 3
                        $output,
232 3
                        $this->parseChildren($ua, $subChild, $lite, $standard)
233
                    );
234
                }
235
            } else {
236 3
                $this->patternId['device'] = '';
237 3
                $output                    = array_merge(
238 3
                    $output,
239 3
                    $this->parseChildren($ua, $child, $lite, $standard)
240
                );
241
            }
242 6
            ++$i;
243
        }
244
245 12
        return $output;
246
    }
247
248
    /**
249
     * Render the children section in a single User Agent block
250
     *
251
     * @param string $ua
252
     * @param array  $uaDataChild
253
     * @param bool   $lite
254
     * @param bool   $standard
255
     *
256
     * @return array[]
257
     */
258 6
    private function parseChildren(string $ua, array $uaDataChild, bool $lite = true, bool $standard = true) : array
259
    {
260 6
        $output = [];
261
262 6
        if (isset($uaDataChild['platforms']) && is_array($uaDataChild['platforms'])) {
263 2
            foreach ($uaDataChild['platforms'] as $platform) {
0 ignored issues
show
Bug introduced by
The expression $uaDataChild['platforms'] of type array|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
264 2
                $this->patternId['platform']       = $platform;
265 2
                $properties                        = ['Parent' => $ua, 'lite' => $lite, 'standard' => $standard];
266 2
                $platformProperties                = $this->collection->getPlatform($platform);
267
268 2
                if (!$platformProperties->isLite()) {
269 2
                    $properties['lite'] = false;
270
                }
271
272 2
                if (!$platformProperties->isStandard()) {
273 2
                    $properties['standard'] = false;
274
                }
275
276 2
                $uaBase = str_replace('#PLATFORM#', $platformProperties->getMatch(), $uaDataChild['match']);
277
278 2 View Code Duplication
                if (array_key_exists('engine', $uaDataChild)) {
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...
279
                    $engine           = $this->collection->getEngine($uaDataChild['engine']);
0 ignored issues
show
Documentation introduced by
$uaDataChild['engine'] is of type array|null, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
280
                    $engineProperties = $engine->getProperties();
281
                } else {
282 2
                    $engineProperties = [];
283
                }
284
285 2 View Code Duplication
                if (array_key_exists('device', $uaDataChild)) {
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...
286 1
                    $device           = $this->collection->getDevice($uaDataChild['device']);
0 ignored issues
show
Documentation introduced by
$uaDataChild['device'] is of type array|null, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
287 1
                    $deviceProperties = $device->getProperties();
288
289 1
                    if (!$device->isStandard()) {
290 1
                        $properties['standard'] = false;
291
                    }
292
                } else {
293 1
                    $deviceProperties = [];
294
                }
295
296 2
                $properties = array_merge(
297 2
                    $properties,
298 2
                    $engineProperties,
299 2
                    $deviceProperties,
300 2
                    $platformProperties->getProperties()
301
                );
302
303 2
                if (isset($uaDataChild['properties'])
304 2
                    && is_array($uaDataChild['properties'])
305
                ) {
306 2
                    $childProperties = $uaDataChild['properties'];
307
308 2
                    $properties = array_merge($properties, $childProperties);
309
                }
310
311 2
                $properties['PatternId'] = $this->getPatternId();
312
313 2
                $output[$uaBase] = $properties;
314
            }
315
        } else {
316 4
            $properties = ['Parent' => $ua, 'lite' => $lite, 'standard' => $standard];
317
318 4 View Code Duplication
            if (array_key_exists('engine', $uaDataChild)) {
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...
319
                $engine           = $this->collection->getEngine($uaDataChild['engine']);
320
                $engineProperties = $engine->getProperties();
321
            } else {
322 4
                $engineProperties = [];
323
            }
324
325 4 View Code Duplication
            if (array_key_exists('device', $uaDataChild)) {
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...
326 2
                $device           = $this->collection->getDevice($uaDataChild['device']);
327 2
                $deviceProperties = $device->getProperties();
328
329 2
                if (!$device->isStandard()) {
330 2
                    $properties['standard'] = false;
331
                }
332
            } else {
333 2
                $deviceProperties = [];
334
            }
335
336 4
            $properties = array_merge($properties, $engineProperties, $deviceProperties);
337
338 4
            if (isset($uaDataChild['properties'])
339 4
                && is_array($uaDataChild['properties'])
340
            ) {
341 4
                $childProperties = $uaDataChild['properties'];
342
343 4
                $this->checkPlatformData->check(
344 4
                    $childProperties,
345 4
                    'the properties array contains platform data for key "' . $ua
346 4
                    . '", please use the "platforms" keyword'
347
                );
348
349 4
                $this->checkEngineData->check(
350 4
                    $childProperties,
351 4
                    'the properties array contains engine data for key "' . $ua
352 4
                    . '", please use the "engine" keyword'
353
                );
354
355 4
                $this->checkDeviceData->check(
356 4
                    $childProperties,
357 4
                    'the properties array contains device data for key "' . $ua
358 4
                    . '", please use the "device" or the "devices" keyword'
359
                );
360
361 4
                $properties = array_merge($properties, $childProperties);
362
            }
363
364 4
            $uaBase                      = str_replace('#PLATFORM#', '', $uaDataChild['match']);
365 4
            $this->patternId['platform'] = '';
366
367 4
            $properties['PatternId'] = $this->getPatternId();
368
369 4
            $output[$uaBase] = $properties;
370
        }
371
372 6
        return $output;
373
    }
374
375
    /**
376
     * Builds and returns the string pattern id from the array components
377
     *
378
     * @return string
379
     */
380 6
    private function getPatternId() : string
381
    {
382 6
        return sprintf(
383 6
            '%s::u%d::c%d::d%s::p%s',
384 6
            $this->patternId['division'],
385 6
            $this->patternId['useragent'],
386 6
            $this->patternId['child'],
387 6
            $this->patternId['device'],
388 6
            $this->patternId['platform']
389
        );
390
    }
391
392
    /**
393
     * expands all properties for all useragents to make sure all properties are set and make it possible to skip
394
     * incomplete properties and remove duplicate definitions
395
     *
396
     * @param array $allInputDivisions
397
     *
398
     * @throws \UnexpectedValueException
399
     *
400
     * @return array
401
     */
402 13
    private function expandProperties(array $allInputDivisions) : array
403
    {
404 13
        $this->logger->debug('expand all properties');
405 13
        $allDivisions = [];
406
407 13
        $ua                = $this->collection->getDefaultProperties()->getUserAgents()[0];
408 13
        $defaultproperties = $ua->getProperties();
409
410 13
        foreach (array_keys($allInputDivisions) as $key) {
411 12
            $this->logger->debug('expand all properties for key "' . $key . '"');
412
413 12
            $userAgent = $key;
414 12
            $parents   = [$userAgent];
415
416 12
            while (isset($allInputDivisions[$userAgent]['Parent'])) {
417 12
                if ($allInputDivisions[$userAgent]['Parent'] === $userAgent) {
418
                    break;
419
                }
420
421 12
                $parents[] = $allInputDivisions[$userAgent]['Parent'];
422 12
                $userAgent = $allInputDivisions[$userAgent]['Parent'];
423
            }
424 12
            unset($userAgent);
425
426 12
            $parents     = array_reverse($parents);
427 12
            $browserData = $defaultproperties;
428 12
            $properties  = $allInputDivisions[$key];
429
430 12
            foreach ($parents as $parent) {
431 12
                if (!isset($allInputDivisions[$parent])) {
432 12
                    continue;
433
                }
434
435 12
                if (!is_array($allInputDivisions[$parent])) {
436
                    throw new \UnexpectedValueException(
437
                        'Parent "' . $parent . '" is not an array for key "' . $key . '"'
438
                    );
439
                }
440
441 12
                if ($key !== $parent
442 6
                    && isset($allInputDivisions[$parent]['sortIndex'], $properties['sortIndex'])
443
444
                    && ($allInputDivisions[$parent]['division'] !== $properties['division'])
445
                ) {
446
                    if ($allInputDivisions[$parent]['sortIndex'] >= $properties['sortIndex']) {
447
                        throw new \UnexpectedValueException(
448
                            'sorting not ready for key "'
449
                            . $key . '"'
450
                        );
451
                    }
452
                }
453
454 12
                $browserData = array_merge($browserData, $allInputDivisions[$parent]);
455
            }
456
457 12
            array_pop($parents);
458 12
            $browserData['Parents'] = implode(',', $parents);
459 12
            unset($parents);
460
461 12
            foreach (array_keys($browserData) as $propertyName) {
462 12
                if (is_bool($browserData[$propertyName])) {
463 12
                    $properties[$propertyName] = $browserData[$propertyName];
464
                } else {
465 12
                    $properties[$propertyName] = $this->trimProperty->trimProperty((string) $browserData[$propertyName]);
466
                }
467
            }
468
469 12
            unset($browserData);
470
471 12
            $allDivisions[$key] = $properties;
472
473 12
            if (!isset($properties['Version'])) {
474
                throw new \UnexpectedValueException('Version property not found for key "' . $key . '"');
475
            }
476
477 12
            $completeVersions = explode('.', $properties['Version'], 2);
478
479 12
            $properties['MajorVer'] = (string) $completeVersions[0];
480
481 12
            if (isset($completeVersions[1])) {
482 12
                $minorVersion = (string) $completeVersions[1];
483
            } else {
484
                $minorVersion = '0';
485
            }
486
487 12
            $properties['MinorVer'] = $minorVersion;
488
489 12
            $allDivisions[$key] = $properties;
490
        }
491
492 13
        return $allDivisions;
493
    }
494
}
495