Skip to content

Commit e643785

Browse files
committed
version 2
1 parent ae83c73 commit e643785

19 files changed

+1476
-699
lines changed

README.md

Lines changed: 20 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,20 @@
1-
Version Helper for PHP projects
2-
===============================
3-
4-
This module normalize versions as Composer do. Parse constraints as Composer do too.
5-
And tests if a version match a constraint.
6-
7-
Example:
8-
9-
```php
10-
<?php
11-
12-
$parser = new \Version\VersionParser();
13-
14-
echo $parser->parseStability('1.2-RC2'); // RC
15-
echo $parser->parseStability('2.0b'); // beta
16-
echo $parser->parseConstraints('1.0'); // stable
17-
18-
echo $parser->normalize('2.0b1'); // 2.0.0.0-beta1
19-
20-
$c = $parser->parseConstraints('>=1.2.5,<2.0');
21-
echo $c->match('1.2.0'); // false
22-
echo $c->match('1.5'); // true
23-
echo $c->match('2.0'); // false
24-
25-
?>
26-
```
1+
Versions and Constraints for PHP
2+
================================
3+
4+
This library parse versions,
5+
E.x.:
6+
<code>1.0.0</code>
7+
<code>1.0.2-stable</code>
8+
<code>1.0.20-alpha2</code>.
9+
It can parse constraints (like Composer versions),
10+
E.x.:
11+
<code>>=1.0 >=1.0,<2.0 >=1.0,<1.1 | >=1.2</code>,
12+
<code>1.0.*</code>,
13+
<code>~1.2</code>.
14+
15+
The goal of that is to let you check if a version matches a constraint,
16+
or to check if a constraint is a subset of another constraint.
17+
18+
All that is done to let us select which version is compatible with a user constraints.
19+
20+
It works with the same rules of Composer versioning.

src/Version/Constraint.php

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
<?php
2+
3+
namespace Version;
4+
5+
use Version\Constraint\AnythingConstraint;
6+
use Version\Constraint\MultiConstraint;
7+
use Version\Constraint\SimpleConstraint;
8+
9+
abstract class Constraint
10+
{
11+
public abstract function matches(Constraint $constraint);
12+
13+
/**
14+
* @param string $input
15+
* @return Constraint
16+
*/
17+
public static function parse($input)
18+
{
19+
$input = trim($input);
20+
21+
if (strlen($input) == 0) {
22+
throw new \UnexpectedValueException('Empty.');
23+
}
24+
25+
$input = explode(',', $input);
26+
if (count($input) > 1) {
27+
$and = true;
28+
} else {
29+
$input = explode('|', $input[0]);
30+
$and = false;
31+
}
32+
33+
if (count($input) > 1) {
34+
$constraints = array();
35+
foreach ($input as $constraint) {
36+
$constraints[] = self::parse($constraint);
37+
}
38+
return new MultiConstraint($constraints, $and);
39+
}
40+
41+
$input = $input[0];
42+
43+
$regex = '/^' .
44+
'(?:([\*|x])\.)?' .
45+
'(?:([\*|x])\.)?' .
46+
'(?:([\*|x])\.)?' .
47+
'(?:([\*|x]))?' .
48+
'$/';
49+
50+
if (preg_match($regex, $input, $matches)) {
51+
return new AnythingConstraint();
52+
}
53+
54+
$regex = '/^' .
55+
'(?:(' . Operator::REGEX . '))? *' .
56+
'(?:(\d+|\*|x)\.)?' .
57+
'(?:(\d+|\*|x)\.)?' .
58+
'(?:(\d+|\*|x)\.)?' .
59+
'(?:(\d+|\*|x))?' .
60+
'(?:' . Stability::REGEX . ')?' .
61+
'$/';
62+
63+
if(!preg_match($regex, $input, $matches)) {
64+
throw new \UnexpectedValueException('Invalid type: ' . $input);
65+
}
66+
67+
if (isset($matches[1]) && strlen($matches[1]) > 0) {
68+
$operator = $matches[1];
69+
} else {
70+
$operator = '=';
71+
}
72+
$operator = new Operator($operator);
73+
74+
$parts = array();
75+
76+
if (isset($matches[2]) && strlen($matches[2]) > 0) $parts[] = $matches[2];
77+
if (isset($matches[3]) && strlen($matches[3]) > 0) $parts[] = $matches[3];
78+
if (isset($matches[4]) && strlen($matches[4]) > 0) $parts[] = $matches[4];
79+
if (isset($matches[5]) && strlen($matches[5]) > 0) $parts[] = $matches[5];
80+
81+
if ((string)$operator == '~') {
82+
$end = count($parts);
83+
} else {
84+
$end = null;
85+
}
86+
87+
while (count($parts) < 4) {
88+
$parts[] = 0;
89+
}
90+
91+
$max = $parts;
92+
93+
if ($end) {
94+
if ($end == 1) {
95+
$max[0]++;
96+
} elseif ($end == 2) {
97+
$max[0]++;
98+
$max[1] = 0;
99+
} elseif ($end == 3) {
100+
$max[1]++;
101+
$max[2] = 0;
102+
} elseif ($end == 4) {
103+
$max[2]++;
104+
$max[3] = 0;
105+
} else {
106+
echo $end;
107+
die($end);
108+
}
109+
}
110+
111+
if ($parts[3] === 'x' || $parts[3] === '*') {
112+
$parts[3] = 0;
113+
$max[3] = 0;
114+
$max[2]++;
115+
}
116+
117+
if ($parts[2] === 'x' || $parts[2] === '*') {
118+
$parts[2] = 0;
119+
$max[2] = 0;
120+
$max[1]++;
121+
}
122+
123+
if ($parts[1] === 'x' || $parts[1] === '*') {
124+
$parts[1] = 0;
125+
$max[1] = 0;
126+
$max[0]++;
127+
}
128+
129+
$version = new Version($parts[0]);
130+
if (isset($parts[1])) $version->setMinor($parts[1]);
131+
if (isset($parts[2])) $version->setRevision($parts[2]);
132+
if (isset($parts[3])) $version->setMicro($parts[3]);
133+
134+
if (isset($matches[6]) && strlen($matches[6]) > 0) {
135+
if (strtolower($matches[5]) == 'rc') {
136+
$stability = 'RC';
137+
} elseif (in_array(strtolower($matches[6]), array('pl', 'patch', 'p'))) {
138+
$stability = 'patch';
139+
} elseif (in_array(strtolower($matches[6]), array('beta', 'b'))) {
140+
$stability = 'beta';
141+
} elseif (strtolower($matches[6]) == 'stable') {
142+
$stability = 'stable';
143+
} else {
144+
throw new \UnexpectedValueException('Invalid type: ' . $input);
145+
}
146+
$version->setStability(new Stability($stability, $matches[7]));
147+
}
148+
149+
foreach ($parts as $k => $v) {
150+
if ($v != $max[$k]) {
151+
if ($input == '<=1.2.3') {
152+
print_r($parts);
153+
print_r($max);
154+
print_r(array_diff($parts, $max));
155+
die;
156+
}
157+
$maxVersion = new Version($max[0]);
158+
if (isset($max[1])) $maxVersion->setMinor($max[1]);
159+
if (isset($max[2])) $maxVersion->setRevision($max[2]);
160+
if (isset($max[3])) $maxVersion->setMicro($max[3]);
161+
162+
if ((string)$version == '0.0.0.0') {
163+
return new SimpleConstraint(new Operator('<'), $maxVersion);
164+
}
165+
if (isset($matches[6]) && strtolower($matches[6]) == 'stable') {
166+
$version->setStability(new Stability());
167+
}
168+
return new MultiConstraint(array(
169+
new SimpleConstraint(new Operator('>='), $version),
170+
new SimpleConstraint(new Operator('<'), $maxVersion)
171+
));
172+
}
173+
}
174+
175+
return new SimpleConstraint($operator, $version);
176+
}
177+
178+
abstract public function isSubsetOf(Constraint $constraint);
179+
180+
public function isIncluding(Constraint $constraint)
181+
{
182+
return $constraint->isSubsetOf($this);
183+
}
184+
}

src/Version/Constraint/AbstractConstraint.php

Lines changed: 0 additions & 8 deletions
This file was deleted.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace Version\Constraint;
4+
5+
use Version\Constraint;
6+
7+
class AnythingConstraint extends Constraint
8+
{
9+
public function __toString()
10+
{
11+
return '*';
12+
}
13+
14+
public function matches(Constraint $constraint)
15+
{
16+
return true;
17+
}
18+
19+
public function isSubsetOf(Constraint $constraint)
20+
{
21+
throw new \Exception('Constraint comparison of * with constraint ' .
22+
$constraint . ' Not implemented yet');
23+
}
24+
}

src/Version/Constraint/EmptyConstraint.php

Lines changed: 0 additions & 16 deletions
This file was deleted.

src/Version/Constraint/MultiConstraint.php

Lines changed: 67 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,90 @@
22

33
namespace Version\Constraint;
44

5-
class MultiConstraint
5+
use Version\Constraint;
6+
7+
class MultiConstraint extends Constraint
68
{
7-
public function __construct(array $minMax, $and = true)
9+
/**
10+
* @var Constraint[]
11+
*/
12+
private $constraints;
13+
14+
public function __construct(array $constraints, $and = true)
815
{
9-
$this->minMax = $minMax;
16+
$this->constraints = $constraints;
1017
$this->and = $and;
1118
}
1219

1320
public function __toString()
1421
{
15-
return implode($this->and ? ',' : '|', $this->minMax);
22+
return implode($this->and ? ',' : '|', $this->constraints);
1623
}
1724

18-
public function match($version)
25+
public function matches(Constraint $constraint)
1926
{
20-
if($this->and) {
21-
foreach($this->minMax as $c) {
22-
if(!$c->match($version))
27+
if ($this->and) {
28+
foreach ($this->constraints as $c) {
29+
if (!$c->matches($constraint))
2330
return false;
2431
}
2532
return true;
2633
} else {
27-
foreach($this->minMax as $c) {
28-
if($c->match($version))
34+
foreach ($this->constraints as $c) {
35+
if ($c->matches($constraint))
36+
return true;
37+
}
38+
return false;
39+
}
40+
}
41+
42+
public function isSubsetOf(Constraint $constraint)
43+
{
44+
if($constraint instanceof SimpleConstraint) {
45+
foreach ($this->constraints as $child) {
46+
if ($child->isSubsetOf($constraint)) {
2947
return true;
48+
}
3049
}
3150
return false;
3251
}
52+
if($constraint instanceof MultiConstraint) {
53+
if(count($this->constraints) == 2) {
54+
if(count($constraint->constraints) == 2) {
55+
$min1 = $this->constraints[0];
56+
$max1 = $this->constraints[1];
57+
$min2 = $constraint->constraints[0];
58+
$max2 = $constraint->constraints[1];
59+
if(
60+
$min1 instanceof SimpleConstraint &&
61+
$min2 instanceof SimpleConstraint &&
62+
$max1 instanceof SimpleConstraint &&
63+
$max2 instanceof SimpleConstraint
64+
){
65+
if(
66+
in_array((string) $min1->getOperator() , array('>', '>=')) &&
67+
in_array((string) $min2->getOperator() , array('>', '>=')) &&
68+
in_array((string) $max1->getOperator() , array('<', '<=')) &&
69+
in_array((string) $max2->getOperator() , array('<', '<='))
70+
) {
71+
return
72+
$min1->isSubsetOf($min2) &&
73+
$max1->isSubsetOf($max2);
74+
}
75+
}
76+
}
77+
}
78+
}
79+
throw new \Exception('Constraint comparison by ' .
80+
$this . ' with constraint ' . $constraint .
81+
' Not implemented yet');
82+
}
83+
84+
/**
85+
* @return Constraint[]
86+
*/
87+
public function getConstraints()
88+
{
89+
return $this->constraints;
3390
}
3491
}

0 commit comments

Comments
 (0)