Completed
Push — master ( 7f0b35...06373b )
by brian
04:51
created

HyphenatedRangeParser   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 216
Duplicated Lines 0 %

Coupling/Cohesion

Dependencies 6

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 20
cbo 6
dl 0
loc 216
ccs 0
cts 77
cp 0
rs 10
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 1
B canParse() 0 17 5
B parse() 0 42 5
B chunk() 0 25 4
A getLowerConstraint() 0 7 1
B getUpperConstraint() 0 40 4
1
<?php
2
3
/**
4
 * @copyright   (c) 2014-2017 brian ridley
5
 * @author      brian ridley <[email protected]>
6
 * @license     http://opensource.org/licenses/MIT MIT
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace ptlis\SemanticVersion\Parse\Matcher;
13
14
use ptlis\SemanticVersion\Comparator\ComparatorInterface;
15
use ptlis\SemanticVersion\Parse\Token;
16
use ptlis\SemanticVersion\Version\Label\LabelBuilder;
17
use ptlis\SemanticVersion\Version\Version;
18
use ptlis\SemanticVersion\Version\VersionBuilder;
19
use ptlis\SemanticVersion\VersionRange\ComparatorVersion;
20
use ptlis\SemanticVersion\VersionRange\LogicalAnd;
21
use ptlis\SemanticVersion\VersionRange\VersionRangeInterface;
22
23
/**
24
 * Parser for hyphenated ranges.
25
 *
26
 * Hyphenated ranges are implemented as described @ https://getcomposer.org/doc/articles/versions.md#range-hyphen-
27
 */
28
final class HyphenatedRangeParser implements RangeParserInterface
29
{
30
    /**
31
     * @var VersionBuilder
32
     */
33
    private $versionBuilder;
34
35
    /**
36
     * @var ComparatorInterface
37
     */
38
    private $greaterOrEqualTo;
39
40
    /**
41
     * @var ComparatorInterface
42
     */
43
    private $lessThan;
44
45
    /**
46
     * @var ComparatorInterface
47
     */
48
    private $lessOrEqualTo;
49
50
51
    /**
52
     * Constructor.
53
     *
54
     * @param VersionBuilder $versionBuilder
55
     * @param ComparatorInterface $greaterOrEqualTo
56
     * @param ComparatorInterface $lessThan
57
     * @param ComparatorInterface $lessOrEqualTo
58
     */
59
    public function __construct(
60
        VersionBuilder $versionBuilder,
61
        ComparatorInterface $greaterOrEqualTo,
62
        ComparatorInterface $lessThan,
63
        ComparatorInterface $lessOrEqualTo
64
    ) {
65
        $this->versionBuilder = $versionBuilder;
66
        $this->greaterOrEqualTo = $greaterOrEqualTo;
67
        $this->lessThan = $lessThan;
68
        $this->lessOrEqualTo = $lessOrEqualTo;
69
    }
70
71
    /**
72
     * Returns true if the token list can be parsed as a hyphenated range.
73
     *
74
     * @param Token[] $tokenList
75
     *
76
     * @return boolean
77
     */
78
    public function canParse(array $tokenList)
79
    {
80
        $isRange = false;
81
82
        for ($i = 0; $i < count($tokenList); $i++) {
1 ignored issue
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
83
            $token = $tokenList[$i];
84
            if (
85
                Token::DASH_SEPARATOR === $token->getType()
86
                && $i + 1 < count($tokenList)
87
                && Token::LABEL_STRING !== $tokenList[$i]
88
            ) {
89
                $isRange = true;
90
            }
91
        }
92
93
        return $isRange;
94
    }
95
96
    /**
97
     * Build a ComparatorVersion representing the hyphenated range.
98
     *
99
     * @param Token[] $tokenList
100
     *
101
     * @return VersionRangeInterface
102
     */
103
    public function parse(array $tokenList)
104
    {
105
        $chunkedList = $this->chunk($tokenList);
106
107
        switch (count($chunkedList)) {
1 ignored issue
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
108
109
            // No labels
110
            case 2:
111
                $lowerVersionConstraint = $this->getLowerConstraint($chunkedList[0]);
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $lowerVersionConstraint exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
112
                $upperVersionConstraint = $this->getUpperConstraint($chunkedList[1]);
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $upperVersionConstraint exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
113
                break;
114
115
            // Label on one version
116
            case 3:
117
                // Label belongs to first version
118
                if (Token::LABEL_STRING === $chunkedList[1][0]->getType()) {
119
                    $lowerVersionConstraint = $this->getLowerConstraint($chunkedList[0], $chunkedList[1]);
120
                    $upperVersionConstraint = $this->getUpperConstraint($chunkedList[2]);
121
122
                // Label belongs to second version
123
                } else {
124
                    $lowerVersionConstraint = $this->getLowerConstraint($chunkedList[0]);
125
                    $upperVersionConstraint = $this->getUpperConstraint($chunkedList[1], $chunkedList[2]);
126
                }
127
128
                break;
129
130
            // Label on both versions
131
            case 4:
132
                $lowerVersionConstraint = $this->getLowerConstraint($chunkedList[0], $chunkedList[1]);
133
                $upperVersionConstraint = $this->getUpperConstraint($chunkedList[2], $chunkedList[3]);
134
                break;
135
136
            default:
137
                throw new \RuntimeException('Invalid version');
138
        }
139
140
        return new LogicalAnd(
141
            $lowerVersionConstraint,
142
            $upperVersionConstraint
143
        );
144
    }
145
146
    /**
147
     * Chuck the tokens, splitting on hyphen.
148
     *
149
     * @param Token[] $tokenList
150
     *
151
     * @return Token[][]
152
     */
153
    private function chunk(array $tokenList)
154
    {
155
        $tokenListCount = count($tokenList);
156
        $chunkedList = [];
157
        $accumulator = [];
158
159
        for ($i = 0; $i < $tokenListCount; $i++) {
160
            $token = $tokenList[$i];
161
162
            // Accumulate until we hit a dash
163
            if (Token::DASH_SEPARATOR !== $token->getType()) {
164
                $accumulator[] = $token;
165
1 ignored issue
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
166
            } else {
167
                $chunkedList[] = $accumulator;
168
                $accumulator = [];
169
            }
170
        }
171
172
        if (count($accumulator)) {
173
            $chunkedList[] = $accumulator;
174
        }
175
176
        return $chunkedList;
177
    }
178
179
    /**
180
     * Determines the correct lower version constraint for a hyphenated range.
181
     *
182
     * @param Token[] $versionTokenList
183
     * @param Token[] $labelTokenList
184
     *
185
     * @return VersionRangeInterface
186
     */
187
    private function getLowerConstraint(array $versionTokenList, array $labelTokenList = [])
188
    {
189
        return new ComparatorVersion(
190
            $this->greaterOrEqualTo,
191
            $this->versionBuilder->buildFromTokens($versionTokenList, $labelTokenList)
0 ignored issues
show
Bug introduced by
It seems like $this->versionBuilder->b...nList, $labelTokenList) can be null; however, __construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
192
        );
193
    }
194
195
    /**
196
     * Determines the correct upper version constraint for a hyphenated range.
197
     *
198
     * @param Token[] $versionTokenList
199
     * @param Token[] $labelTokenList
200
     *
201
     * @return VersionRangeInterface
202
     */
203
    private function getUpperConstraint(array $versionTokenList, array $labelTokenList = [])
204
    {
205
        $minor = 0;
206
        $patch = 0;
207
        $label = null;
0 ignored issues
show
Unused Code introduced by
$label 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...
208
        $labelBuilder = new LabelBuilder();
209
210
        switch (count($versionTokenList)) {
211
            case 1:
212
                $comparator = $this->lessThan;
213
                $major = $versionTokenList[0]->getValue() + 1;
214
                break;
215
216
            case 3:
217
                $comparator = $this->lessThan;
218
                $major = $versionTokenList[0]->getValue();
219
                $minor = $versionTokenList[2]->getValue() + 1;
220
                break;
221
222
            case 5:
223
                $comparator = $this->lessOrEqualTo;
224
                $major = $versionTokenList[0]->getValue();
225
                $minor = $versionTokenList[2]->getValue();
226
                $patch = $versionTokenList[4]->getValue();
227
                break;
228
229
            default:
230
                throw new \RuntimeException('Invalid version');
231
        }
232
233
        return new ComparatorVersion(
234
            $comparator,
235
            new Version(
236
                $major,
237
                $minor,
238
                $patch,
239
                $labelBuilder->buildFromTokens($labelTokenList)
240
            )
241
        );
242
    }
243
}
244