Intervals   F
last analyzed

Complexity

Total Complexity 94

Size/Duplication

Total Lines 296
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 94
eloc 195
dl 0
loc 296
rs 2
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A clear() 0 3 1
C isSubsetOf() 0 36 13
A get() 0 7 2
B haveIntersections() 0 10 7
D compactConstraint() 0 86 32
F generateIntervals() 0 105 32
B generateSingleConstraintIntervals() 0 24 7

How to fix   Complexity   

Complex Class

Complex classes like Intervals 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.

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 Intervals, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace HumbugBox451\Composer\Semver;
4
5
use HumbugBox451\Composer\Semver\Constraint\Constraint;
6
use HumbugBox451\Composer\Semver\Constraint\ConstraintInterface;
7
use HumbugBox451\Composer\Semver\Constraint\MatchAllConstraint;
8
use HumbugBox451\Composer\Semver\Constraint\MatchNoneConstraint;
9
use HumbugBox451\Composer\Semver\Constraint\MultiConstraint;
10
/** @internal */
11
class Intervals
12
{
13
    /**
14
    @phpstan-var
15
    */
16
    private static $intervalsCache = array();
17
    /**
18
    @phpstan-var
19
    */
20
    private static $opSortOrder = array('>=' => -3, '<' => -2, '>' => 2, '<=' => 3);
21
    public static function clear()
22
    {
23
        self::$intervalsCache = array();
24
    }
25
    public static function isSubsetOf(ConstraintInterface $candidate, ConstraintInterface $constraint)
26
    {
27
        if ($constraint instanceof MatchAllConstraint) {
28
            return \true;
29
        }
30
        if ($candidate instanceof MatchNoneConstraint || $constraint instanceof MatchNoneConstraint) {
31
            return \false;
32
        }
33
        $intersectionIntervals = self::get(new MultiConstraint(array($candidate, $constraint), \true));
34
        $candidateIntervals = self::get($candidate);
35
        if (\count($intersectionIntervals['numeric']) !== \count($candidateIntervals['numeric'])) {
36
            return \false;
37
        }
38
        foreach ($intersectionIntervals['numeric'] as $index => $interval) {
39
            if (!isset($candidateIntervals['numeric'][$index])) {
40
                return \false;
41
            }
42
            if ((string) $candidateIntervals['numeric'][$index]->getStart() !== (string) $interval->getStart()) {
43
                return \false;
44
            }
45
            if ((string) $candidateIntervals['numeric'][$index]->getEnd() !== (string) $interval->getEnd()) {
46
                return \false;
47
            }
48
        }
49
        if ($intersectionIntervals['branches']['exclude'] !== $candidateIntervals['branches']['exclude']) {
50
            return \false;
51
        }
52
        if (\count($intersectionIntervals['branches']['names']) !== \count($candidateIntervals['branches']['names'])) {
53
            return \false;
54
        }
55
        foreach ($intersectionIntervals['branches']['names'] as $index => $name) {
56
            if ($name !== $candidateIntervals['branches']['names'][$index]) {
57
                return \false;
58
            }
59
        }
60
        return \true;
61
    }
62
    public static function haveIntersections(ConstraintInterface $a, ConstraintInterface $b)
63
    {
64
        if ($a instanceof MatchAllConstraint || $b instanceof MatchAllConstraint) {
65
            return \true;
66
        }
67
        if ($a instanceof MatchNoneConstraint || $b instanceof MatchNoneConstraint) {
68
            return \false;
69
        }
70
        $intersectionIntervals = self::generateIntervals(new MultiConstraint(array($a, $b), \true), \true);
71
        return \count($intersectionIntervals['numeric']) > 0 || $intersectionIntervals['branches']['exclude'] || \count($intersectionIntervals['branches']['names']) > 0;
72
    }
73
    public static function compactConstraint(ConstraintInterface $constraint)
74
    {
75
        if (!$constraint instanceof MultiConstraint) {
76
            return $constraint;
77
        }
78
        $intervals = self::generateIntervals($constraint);
79
        $constraints = array();
80
        $hasNumericMatchAll = \false;
81
        if (\count($intervals['numeric']) === 1 && (string) $intervals['numeric'][0]->getStart() === (string) Interval::fromZero() && (string) $intervals['numeric'][0]->getEnd() === (string) Interval::untilPositiveInfinity()) {
82
            $constraints[] = $intervals['numeric'][0]->getStart();
83
            $hasNumericMatchAll = \true;
84
        } else {
85
            $unEqualConstraints = array();
86
            for ($i = 0, $count = \count($intervals['numeric']); $i < $count; $i++) {
87
                $interval = $intervals['numeric'][$i];
88
                if ($interval->getEnd()->getOperator() === '<' && $i + 1 < $count) {
89
                    $nextInterval = $intervals['numeric'][$i + 1];
90
                    if ($interval->getEnd()->getVersion() === $nextInterval->getStart()->getVersion() && $nextInterval->getStart()->getOperator() === '>') {
91
                        if (\count($unEqualConstraints) === 0 && (string) $interval->getStart() !== (string) Interval::fromZero()) {
92
                            $unEqualConstraints[] = $interval->getStart();
93
                        }
94
                        $unEqualConstraints[] = new Constraint('!=', $interval->getEnd()->getVersion());
95
                        continue;
96
                    }
97
                }
98
                if (\count($unEqualConstraints) > 0) {
99
                    if ((string) $interval->getEnd() !== (string) Interval::untilPositiveInfinity()) {
100
                        $unEqualConstraints[] = $interval->getEnd();
101
                    }
102
                    if (\count($unEqualConstraints) > 1) {
103
                        $constraints[] = new MultiConstraint($unEqualConstraints, \true);
104
                    } else {
105
                        $constraints[] = $unEqualConstraints[0];
106
                    }
107
                    $unEqualConstraints = array();
108
                    continue;
109
                }
110
                if ($interval->getStart()->getVersion() === $interval->getEnd()->getVersion() && $interval->getStart()->getOperator() === '>=' && $interval->getEnd()->getOperator() === '<=') {
111
                    $constraints[] = new Constraint('==', $interval->getStart()->getVersion());
112
                    continue;
113
                }
114
                if ((string) $interval->getStart() === (string) Interval::fromZero()) {
115
                    $constraints[] = $interval->getEnd();
116
                } elseif ((string) $interval->getEnd() === (string) Interval::untilPositiveInfinity()) {
117
                    $constraints[] = $interval->getStart();
118
                } else {
119
                    $constraints[] = new MultiConstraint(array($interval->getStart(), $interval->getEnd()), \true);
120
                }
121
            }
122
        }
123
        $devConstraints = array();
124
        if (0 === \count($intervals['branches']['names'])) {
125
            if ($intervals['branches']['exclude']) {
126
                if ($hasNumericMatchAll) {
127
                    return new MatchAllConstraint();
128
                }
129
            }
130
        } else {
131
            foreach ($intervals['branches']['names'] as $branchName) {
132
                if ($intervals['branches']['exclude']) {
133
                    $devConstraints[] = new Constraint('!=', $branchName);
134
                } else {
135
                    $devConstraints[] = new Constraint('==', $branchName);
136
                }
137
            }
138
            if ($intervals['branches']['exclude']) {
139
                if (\count($constraints) > 1) {
140
                    return new MultiConstraint(\array_merge(array(new MultiConstraint($constraints, \false)), $devConstraints), \true);
141
                }
142
                if (\count($constraints) === 1 && (string) $constraints[0] === (string) Interval::fromZero()) {
143
                    if (\count($devConstraints) > 1) {
144
                        return new MultiConstraint($devConstraints, \true);
145
                    }
146
                    return $devConstraints[0];
147
                }
148
                return new MultiConstraint(\array_merge($constraints, $devConstraints), \true);
149
            }
150
            $constraints = \array_merge($constraints, $devConstraints);
151
        }
152
        if (\count($constraints) > 1) {
153
            return new MultiConstraint($constraints, \false);
154
        }
155
        if (\count($constraints) === 1) {
156
            return $constraints[0];
157
        }
158
        return new MatchNoneConstraint();
159
    }
160
    /**
161
    @phpstan-return
162
    */
163
    public static function get(ConstraintInterface $constraint)
164
    {
165
        $key = (string) $constraint;
166
        if (!isset(self::$intervalsCache[$key])) {
167
            self::$intervalsCache[$key] = self::generateIntervals($constraint);
168
        }
169
        return self::$intervalsCache[$key];
170
    }
171
    /**
172
    @phpstan-return
173
    */
174
    private static function generateIntervals(ConstraintInterface $constraint, $stopOnFirstValidInterval = \false)
175
    {
176
        if ($constraint instanceof MatchAllConstraint) {
177
            return array('numeric' => array(new Interval(Interval::fromZero(), Interval::untilPositiveInfinity())), 'branches' => Interval::anyDev());
178
        }
179
        if ($constraint instanceof MatchNoneConstraint) {
180
            return array('numeric' => array(), 'branches' => array('names' => array(), 'exclude' => \false));
181
        }
182
        if ($constraint instanceof Constraint) {
183
            return self::generateSingleConstraintIntervals($constraint);
184
        }
185
        if (!$constraint instanceof MultiConstraint) {
186
            throw new \UnexpectedValueException('The constraint passed in should be an MatchAllConstraint, Constraint or MultiConstraint instance, got ' . \get_class($constraint) . '.');
187
        }
188
        $constraints = $constraint->getConstraints();
189
        $numericGroups = array();
190
        $constraintBranches = array();
191
        foreach ($constraints as $c) {
192
            $res = self::get($c);
193
            $numericGroups[] = $res['numeric'];
194
            $constraintBranches[] = $res['branches'];
195
        }
196
        if ($constraint->isDisjunctive()) {
197
            $branches = Interval::noDev();
198
            foreach ($constraintBranches as $b) {
199
                if ($b['exclude']) {
200
                    if ($branches['exclude']) {
201
                        $branches['names'] = \array_intersect($branches['names'], $b['names']);
202
                    } else {
203
                        $branches['exclude'] = \true;
204
                        $branches['names'] = \array_diff($b['names'], $branches['names']);
205
                    }
206
                } else {
207
                    if ($branches['exclude']) {
208
                        $branches['names'] = \array_diff($branches['names'], $b['names']);
209
                    } else {
210
                        $branches['names'] = \array_merge($branches['names'], $b['names']);
211
                    }
212
                }
213
            }
214
        } else {
215
            $branches = Interval::anyDev();
216
            foreach ($constraintBranches as $b) {
217
                if ($b['exclude']) {
218
                    if ($branches['exclude']) {
219
                        $branches['names'] = \array_merge($branches['names'], $b['names']);
220
                    } else {
221
                        $branches['names'] = \array_diff($branches['names'], $b['names']);
222
                    }
223
                } else {
224
                    if ($branches['exclude']) {
225
                        $branches['names'] = \array_diff($b['names'], $branches['names']);
226
                        $branches['exclude'] = \false;
227
                    } else {
228
                        $branches['names'] = \array_intersect($branches['names'], $b['names']);
229
                    }
230
                }
231
            }
232
        }
233
        $branches['names'] = \array_unique($branches['names']);
234
        if (\count($numericGroups) === 1) {
235
            return array('numeric' => $numericGroups[0], 'branches' => $branches);
236
        }
237
        $borders = array();
238
        foreach ($numericGroups as $group) {
239
            foreach ($group as $interval) {
240
                $borders[] = array('version' => $interval->getStart()->getVersion(), 'operator' => $interval->getStart()->getOperator(), 'side' => 'start');
241
                $borders[] = array('version' => $interval->getEnd()->getVersion(), 'operator' => $interval->getEnd()->getOperator(), 'side' => 'end');
242
            }
243
        }
244
        $opSortOrder = self::$opSortOrder;
245
        \usort($borders, function ($a, $b) use($opSortOrder) {
246
            $order = \version_compare($a['version'], $b['version']);
247
            if ($order === 0) {
248
                return $opSortOrder[$a['operator']] - $opSortOrder[$b['operator']];
249
            }
250
            return $order;
251
        });
252
        $activeIntervals = 0;
253
        $intervals = array();
254
        $index = 0;
255
        $activationThreshold = $constraint->isConjunctive() ? \count($numericGroups) : 1;
256
        $start = null;
257
        foreach ($borders as $border) {
258
            if ($border['side'] === 'start') {
259
                $activeIntervals++;
260
            } else {
261
                $activeIntervals--;
262
            }
263
            if (!$start && $activeIntervals >= $activationThreshold) {
264
                $start = new Constraint($border['operator'], $border['version']);
265
            } elseif ($start && $activeIntervals < $activationThreshold) {
266
                if (\version_compare($start->getVersion(), $border['version'], '=') && ($start->getOperator() === '>' && $border['operator'] === '<=' || $start->getOperator() === '>=' && $border['operator'] === '<')) {
267
                    unset($intervals[$index]);
268
                } else {
269
                    $intervals[$index] = new Interval($start, new Constraint($border['operator'], $border['version']));
0 ignored issues
show
Bug introduced by
$start of type void is incompatible with the type HumbugBox451\Composer\Semver\Constraint\Constraint expected by parameter $start of HumbugBox451\Composer\Se...Interval::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

269
                    $intervals[$index] = new Interval(/** @scrutinizer ignore-type */ $start, new Constraint($border['operator'], $border['version']));
Loading history...
270
                    $index++;
271
                    if ($stopOnFirstValidInterval) {
272
                        break;
273
                    }
274
                }
275
                $start = null;
276
            }
277
        }
278
        return array('numeric' => $intervals, 'branches' => $branches);
279
    }
280
    /**
281
    @phpstan-return
282
    */
283
    private static function generateSingleConstraintIntervals(Constraint $constraint)
284
    {
285
        $op = $constraint->getOperator();
286
        if (\strpos($constraint->getVersion(), 'dev-') === 0) {
287
            $intervals = array();
288
            $branches = array('names' => array(), 'exclude' => \false);
289
            if ($op === '!=') {
290
                $intervals[] = new Interval(Interval::fromZero(), Interval::untilPositiveInfinity());
291
                $branches = array('names' => array($constraint->getVersion()), 'exclude' => \true);
292
            } elseif ($op === '==') {
293
                $branches['names'][] = $constraint->getVersion();
294
            }
295
            return array('numeric' => $intervals, 'branches' => $branches);
296
        }
297
        if ($op[0] === '>') {
298
            return array('numeric' => array(new Interval($constraint, Interval::untilPositiveInfinity())), 'branches' => Interval::noDev());
299
        }
300
        if ($op[0] === '<') {
301
            return array('numeric' => array(new Interval(Interval::fromZero(), $constraint)), 'branches' => Interval::noDev());
302
        }
303
        if ($op === '!=') {
304
            return array('numeric' => array(new Interval(Interval::fromZero(), new Constraint('<', $constraint->getVersion())), new Interval(new Constraint('>', $constraint->getVersion()), Interval::untilPositiveInfinity())), 'branches' => Interval::anyDev());
305
        }
306
        return array('numeric' => array(new Interval(new Constraint('>=', $constraint->getVersion()), new Constraint('<=', $constraint->getVersion()))), 'branches' => Interval::noDev());
307
    }
308
}
309