Constraint::getSupportedOperators()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 0
1
<?php
2
3
/*
4
 * This file is part of composer/semver.
5
 *
6
 * (c) Composer <https://github.com/composer>
7
 *
8
 * For the full copyright and license information, please view
9
 * the LICENSE file that was distributed with this source code.
10
 */
11
12
namespace Composer\Semver\Constraint;
13
14
/**
15
 * Defines a constraint.
16
 */
17
class Constraint implements ConstraintInterface
18
{
19
    /* operator integer values */
20
    const OP_EQ = 0;
21
    const OP_LT = 1;
22
    const OP_LE = 2;
23
    const OP_GT = 3;
24
    const OP_GE = 4;
25
    const OP_NE = 5;
26
27
    /* operator string values */
28
    const STR_OP_EQ = '==';
29
    const STR_OP_EQ_ALT = '=';
30
    const STR_OP_LT = '<';
31
    const STR_OP_LE = '<=';
32
    const STR_OP_GT = '>';
33
    const STR_OP_GE = '>=';
34
    const STR_OP_NE = '!=';
35
    const STR_OP_NE_ALT = '<>';
36
37
    /**
38
     * Operator to integer translation table.
39
     *
40
     * @var array
41
     * @phpstan-var array<self::STR_OP_*, self::OP_*>
42
     */
43
    private static $transOpStr = array(
44
        '=' => self::OP_EQ,
45
        '==' => self::OP_EQ,
46
        '<' => self::OP_LT,
47
        '<=' => self::OP_LE,
48
        '>' => self::OP_GT,
49
        '>=' => self::OP_GE,
50
        '<>' => self::OP_NE,
51
        '!=' => self::OP_NE,
52
    );
53
54
    /**
55
     * Integer to operator translation table.
56
     *
57
     * @var array
58
     * @phpstan-var array<self::OP_*, self::STR_OP_*>
59
     */
60
    private static $transOpInt = array(
61
        self::OP_EQ => '==',
62
        self::OP_LT => '<',
63
        self::OP_LE => '<=',
64
        self::OP_GT => '>',
65
        self::OP_GE => '>=',
66
        self::OP_NE => '!=',
67
    );
68
69
    /**
70
     * @var int
71
     * @phpstan-var self::OP_*
72
     */
73
    protected $operator;
74
75
    /** @var string */
76
    protected $version;
77
78
    /** @var string|null */
79
    protected $prettyString;
80
81
    /** @var Bound */
82
    protected $lowerBound;
83
84
    /** @var Bound */
85
    protected $upperBound;
86
87
    /**
88
     * Sets operator and version to compare with.
89
     *
90
     * @param string $operator
91
     * @param string $version
92
     *
93
     * @throws \InvalidArgumentException if invalid operator is given.
94
     *
95
     * @phpstan-param self::STR_OP_* $operator
96
     */
97
    public function __construct($operator, $version)
98
    {
99
        if (!isset(self::$transOpStr[$operator])) {
100
            throw new \InvalidArgumentException(sprintf(
101
                'Invalid operator "%s" given, expected one of: %s',
102
                $operator,
103
                implode(', ', self::getSupportedOperators())
104
            ));
105
        }
106
107
        $this->operator = self::$transOpStr[$operator];
108
        $this->version = $version;
109
    }
110
111
    /**
112
     * @return string
113
     */
114
    public function getVersion()
115
    {
116
        return $this->version;
117
    }
118
119
    /**
120
     * @return string
121
     *
122
     * @phpstan-return self::STR_OP_*
123
     */
124
    public function getOperator()
125
    {
126
        return self::$transOpInt[$this->operator];
127
    }
128
129
    /**
130
     * @param ConstraintInterface $provider
131
     *
132
     * @return bool
133
     */
134
    public function matches(ConstraintInterface $provider)
135
    {
136
        if ($provider instanceof self) {
137
            return $this->matchSpecific($provider);
138
        }
139
140
        // turn matching around to find a match
141
        return $provider->matches($this);
142
    }
143
144
    /**
145
     * {@inheritDoc}
146
     */
147
    public function setPrettyString($prettyString)
148
    {
149
        $this->prettyString = $prettyString;
150
    }
151
152
    /**
153
     * {@inheritDoc}
154
     */
155
    public function getPrettyString()
156
    {
157
        if ($this->prettyString) {
158
            return $this->prettyString;
159
        }
160
161
        return $this->__toString();
162
    }
163
164
    /**
165
     * Get all supported comparison operators.
166
     *
167
     * @return array
168
     *
169
     * @phpstan-return list<self::STR_OP_*>
170
     */
171
    public static function getSupportedOperators()
172
    {
173
        return array_keys(self::$transOpStr);
174
    }
175
176
    /**
177
     * @param  string $operator
178
     * @return int
179
     *
180
     * @phpstan-param  self::STR_OP_* $operator
181
     * @phpstan-return self::OP_*
182
     */
183
    public static function getOperatorConstant($operator)
184
    {
185
        return self::$transOpStr[$operator];
186
    }
187
188
    /**
189
     * @param string $a
190
     * @param string $b
191
     * @param string $operator
192
     * @param bool   $compareBranches
193
     *
194
     * @throws \InvalidArgumentException if invalid operator is given.
195
     *
196
     * @return bool
197
     *
198
     * @phpstan-param self::STR_OP_* $operator
199
     */
200
    public function versionCompare($a, $b, $operator, $compareBranches = false)
201
    {
202
        if (!isset(self::$transOpStr[$operator])) {
203
            throw new \InvalidArgumentException(sprintf(
204
                'Invalid operator "%s" given, expected one of: %s',
205
                $operator,
206
                implode(', ', self::getSupportedOperators())
207
            ));
208
        }
209
210
        $aIsBranch = strpos($a, 'dev-') === 0;
211
        $bIsBranch = strpos($b, 'dev-') === 0;
212
213
        if ($operator === '!=' && ($aIsBranch || $bIsBranch)) {
214
            return $a !== $b;
215
        }
216
217
        if ($aIsBranch && $bIsBranch) {
218
            return $operator === '==' && $a === $b;
219
        }
220
221
        // when branches are not comparable, we make sure dev branches never match anything
222
        if (!$compareBranches && ($aIsBranch || $bIsBranch)) {
223
            return false;
224
        }
225
226
        return \version_compare($a, $b, $operator);
0 ignored issues
show
Bug Best Practice introduced by
The expression return version_compare($a, $b, $operator) also could return the type integer which is incompatible with the documented return type boolean.
Loading history...
227
    }
228
229
    /**
230
     * {@inheritDoc}
231
     */
232
    public function compile($otherOperator)
233
    {
234
        if (strpos($this->version, 'dev-') === 0) {
235
            if (self::OP_EQ === $this->operator) {
236
                if (self::OP_EQ === $otherOperator) {
237
                    return sprintf('$b && $v === %s', \var_export($this->version, true));
238
                }
239
                if (self::OP_NE === $otherOperator) {
240
                    return sprintf('!$b || $v !== %s', \var_export($this->version, true));
241
                }
242
                return 'false';
243
            }
244
245
            if (self::OP_NE === $this->operator) {
246
                if (self::OP_EQ === $otherOperator) {
247
                    return sprintf('!$b || $v !== %s', \var_export($this->version, true));
248
                }
249
                if (self::OP_NE === $otherOperator) {
250
                    return 'true';
251
                }
252
                return '!$b';
253
            }
254
255
            return 'false';
256
        }
257
258
        if (self::OP_EQ === $this->operator) {
259
            if (self::OP_EQ === $otherOperator) {
260
                return sprintf('\version_compare($v, %s, \'==\')', \var_export($this->version, true));
261
            }
262
            if (self::OP_NE === $otherOperator) {
263
                return sprintf('$b || \version_compare($v, %s, \'!=\')', \var_export($this->version, true));
264
            }
265
266
            return sprintf('!$b && \version_compare(%s, $v, \'%s\')', \var_export($this->version, true), self::$transOpInt[$otherOperator]);
267
        }
268
269
        if (self::OP_NE === $this->operator) {
270
            if (self::OP_EQ === $otherOperator) {
271
                return sprintf('$b || (!$b && \version_compare($v, %s, \'!=\'))', \var_export($this->version, true));
272
            }
273
274
            if (self::OP_NE === $otherOperator) {
275
                return 'true';
276
            }
277
            return '!$b';
278
        }
279
280
        if (self::OP_LT === $this->operator || self::OP_LE === $this->operator) {
281
            if (self::OP_LT === $otherOperator || self::OP_LE === $otherOperator) {
282
                return '!$b';
283
            }
284
        } else { // $this->operator must be self::OP_GT || self::OP_GE here
285
            if (self::OP_GT === $otherOperator || self::OP_GE === $otherOperator) {
286
                return '!$b';
287
            }
288
        }
289
290
        if (self::OP_NE === $otherOperator) {
291
            return 'true';
292
        }
293
294
        $codeComparison = sprintf('\version_compare($v, %s, \'%s\')', \var_export($this->version, true), self::$transOpInt[$this->operator]);
295
        if ($this->operator === self::OP_LE) {
296
            if ($otherOperator === self::OP_GT) {
297
                return sprintf('!$b && \version_compare($v, %s, \'!=\') && ', \var_export($this->version, true)) . $codeComparison;
298
            }
299
        } elseif ($this->operator === self::OP_GE) {
300
            if ($otherOperator === self::OP_LT) {
301
                return sprintf('!$b && \version_compare($v, %s, \'!=\') && ', \var_export($this->version, true)) . $codeComparison;
302
            }
303
        }
304
305
        return sprintf('!$b && %s', $codeComparison);
306
    }
307
308
    /**
309
     * @param Constraint $provider
310
     * @param bool       $compareBranches
311
     *
312
     * @return bool
313
     */
314
    public function matchSpecific(Constraint $provider, $compareBranches = false)
315
    {
316
        $noEqualOp = str_replace('=', '', self::$transOpInt[$this->operator]);
317
        $providerNoEqualOp = str_replace('=', '', self::$transOpInt[$provider->operator]);
318
319
        $isEqualOp = self::OP_EQ === $this->operator;
320
        $isNonEqualOp = self::OP_NE === $this->operator;
321
        $isProviderEqualOp = self::OP_EQ === $provider->operator;
322
        $isProviderNonEqualOp = self::OP_NE === $provider->operator;
323
324
        // '!=' operator is match when other operator is not '==' operator or version is not match
325
        // these kinds of comparisons always have a solution
326
        if ($isNonEqualOp || $isProviderNonEqualOp) {
327
            if ($isNonEqualOp && !$isProviderNonEqualOp && !$isProviderEqualOp && strpos($provider->version, 'dev-') === 0) {
328
                return false;
329
            }
330
331
            if ($isProviderNonEqualOp && !$isNonEqualOp && !$isEqualOp && strpos($this->version, 'dev-') === 0) {
332
                return false;
333
            }
334
335
            if (!$isEqualOp && !$isProviderEqualOp) {
336
                return true;
337
            }
338
            return $this->versionCompare($provider->version, $this->version, '!=', $compareBranches);
339
        }
340
341
        // an example for the condition is <= 2.0 & < 1.0
342
        // these kinds of comparisons always have a solution
343
        if ($this->operator !== self::OP_EQ && $noEqualOp === $providerNoEqualOp) {
344
            return !(strpos($this->version, 'dev-') === 0 || strpos($provider->version, 'dev-') === 0);
345
        }
346
347
        $version1 = $isEqualOp ? $this->version : $provider->version;
348
        $version2 = $isEqualOp ? $provider->version : $this->version;
349
        $operator = $isEqualOp ? $provider->operator : $this->operator;
350
351
        if ($this->versionCompare($version1, $version2, self::$transOpInt[$operator], $compareBranches)) {
352
            // special case, e.g. require >= 1.0 and provide < 1.0
353
            // 1.0 >= 1.0 but 1.0 is outside of the provided interval
354
355
            return !(self::$transOpInt[$provider->operator] === $providerNoEqualOp
356
                && self::$transOpInt[$this->operator] !== $noEqualOp
357
                && \version_compare($provider->version, $this->version, '=='));
358
        }
359
360
        return false;
361
    }
362
363
    /**
364
     * @return string
365
     */
366
    public function __toString()
367
    {
368
        return self::$transOpInt[$this->operator] . ' ' . $this->version;
369
    }
370
371
    /**
372
     * {@inheritDoc}
373
     */
374
    public function getLowerBound()
375
    {
376
        $this->extractBounds();
377
378
        return $this->lowerBound;
379
    }
380
381
    /**
382
     * {@inheritDoc}
383
     */
384
    public function getUpperBound()
385
    {
386
        $this->extractBounds();
387
388
        return $this->upperBound;
389
    }
390
391
    /**
392
     * @return void
393
     */
394
    private function extractBounds()
395
    {
396
        if (null !== $this->lowerBound) {
397
            return;
398
        }
399
400
        // Branches
401
        if (strpos($this->version, 'dev-') === 0) {
402
            $this->lowerBound = Bound::zero();
403
            $this->upperBound = Bound::positiveInfinity();
404
405
            return;
406
        }
407
408
        switch ($this->operator) {
409
            case self::OP_EQ:
410
                $this->lowerBound = new Bound($this->version, true);
411
                $this->upperBound = new Bound($this->version, true);
412
                break;
413
            case self::OP_LT:
414
                $this->lowerBound = Bound::zero();
415
                $this->upperBound = new Bound($this->version, false);
416
                break;
417
            case self::OP_LE:
418
                $this->lowerBound = Bound::zero();
419
                $this->upperBound = new Bound($this->version, true);
420
                break;
421
            case self::OP_GT:
422
                $this->lowerBound = new Bound($this->version, false);
423
                $this->upperBound = Bound::positiveInfinity();
424
                break;
425
            case self::OP_GE:
426
                $this->lowerBound = new Bound($this->version, true);
427
                $this->upperBound = Bound::positiveInfinity();
428
                break;
429
            case self::OP_NE:
430
                $this->lowerBound = Bound::zero();
431
                $this->upperBound = Bound::positiveInfinity();
432
                break;
433
        }
434
    }
435
}
436