Skip to content

Commit

Permalink
Convert interval arrays to a value object
Browse files Browse the repository at this point in the history
  • Loading branch information
Seldaek committed May 19, 2020
1 parent 3d72ad3 commit c3e5429
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 59 deletions.
96 changes: 96 additions & 0 deletions src/Interval.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?php

/*
* This file is part of composer/semver.
*
* (c) Composer <https://github.com/composer>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/

namespace Composer\Semver;

use Composer\Semver\Constraint\Constraint;

class Interval
{
/** @var Constraint */
private $start;
/** @var Constraint */
private $end;

public function __construct(Constraint $start, Constraint $end)
{
$this->start = $start;
$this->end = $end;
}

/**
* @return Constraint
*/
public function getStart()
{
return $this->start;
}

/**
* @return Constraint
*/
public function getEnd()
{
return $this->end;
}

/**
* @return Constraint
*/
public static function zero()
{
static $zero;

if (null === $zero) {
$zero = new Constraint('>=', '0.0.0.0-dev');
}

return $zero;
}

/**
* @return Constraint
*/
public static function positiveInfinity()
{
static $positiveInfinity;

if (null === $positiveInfinity) {
$positiveInfinity = new Constraint('<', PHP_INT_MAX.'.0.0.0');
}

return $positiveInfinity;
}

/**
* @return self
*/
public static function any()
{
return new self(self::zero(), self::positiveInfinity());
}

/**
* @return Constraint
*/
public static function anyDev()
{
static $anyDev;

if (null === $anyDev) {
// this ideally should be an EmptyConstraint but the code expects Constraint instances so
// this makes it work with less workarounds/checks above
$anyDev = new Constraint('==', 'dev*');
}

return $anyDev;
}
}
86 changes: 27 additions & 59 deletions src/Intervals.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
class Intervals
{
/**
* @phpstan-var array<string, array{'intervals': array<int, array{'start': Constraint, 'end': Constraint}>, 'devConstraints': Constraint[]}>
* @phpstan-var array<string, array{'intervals': Interval[], 'devConstraints': Constraint[]}>
*/
private static $intervalsCache = array();

Expand Down Expand Up @@ -68,11 +68,11 @@ public static function isSubsetOf(ConstraintInterface $candidate, ConstraintInte
return false;
}

if ((string) $candidateIntervals['intervals'][$index]['start'] !== (string) $interval['start']) {
if ((string) $candidateIntervals['intervals'][$index]->getStart() !== (string) $interval->getStart()) {
return false;
}

if ((string) $candidateIntervals['intervals'][$index]['end'] !== (string) $interval['end']) {
if ((string) $candidateIntervals['intervals'][$index]->getEnd() !== (string) $interval->getEnd()) {
return false;
}
}
Expand Down Expand Up @@ -110,10 +110,10 @@ public static function haveIntersections(ConstraintInterface $a, ConstraintInter
*
* if the returned intervals array is empty it means the constraint matches nothing in the numeric range (0 - +inf)
* if the returned devConstraints array is empty it means no dev-* versions are matched
* if a constraint matches all possible dev-* versions, devConstraints will contain self::anyDev() as a constraint
* if a constraint matches all possible dev-* versions, devConstraints will contain Interval::anyDev() as a constraint
*
* @return array
* @phpstan-return array{'intervals': array<int, array{'start': Constraint, 'end': Constraint}>, 'devConstraints': Constraint[]}
* @phpstan-return array{'intervals': Interval[], 'devConstraints': Constraint[]}
*/
public static function get(ConstraintInterface $constraint)
{
Expand All @@ -127,12 +127,12 @@ public static function get(ConstraintInterface $constraint)
}

/**
* @phpstan-return array{'intervals': array<int, array{'start': Constraint, 'end': Constraint}>, 'devConstraints': Constraint[]}
* @phpstan-return array{'intervals': Interval[], 'devConstraints': Constraint[]}
*/
private static function generateIntervals(ConstraintInterface $constraint, $stopOnFirstValidInterval = false)
{
if ($constraint instanceof EmptyConstraint) {
return array('intervals' => array(array('start' => self::zero(), 'end' => self::positiveInfinity())), 'devConstraints' => array(self::anyDev()));
return array('intervals' => array(new Interval(Interval::zero(), Interval::positiveInfinity())), 'devConstraints' => array(Interval::anyDev()));
}

if ($constraint instanceof Constraint) {
Expand Down Expand Up @@ -164,7 +164,7 @@ private static function generateIntervals(ConstraintInterface $constraint, $stop
}

foreach ($dev as $i => $c) {
if ($c === self::anyDev()) {
if ($c === Interval::anyDev()) {
$dev = array($c);
break;
}
Expand Down Expand Up @@ -210,7 +210,7 @@ private static function generateIntervals(ConstraintInterface $constraint, $stop
}

foreach ($group2 as $j2 => $c2) {
if ((string) $c2 === (string) $c || $c2 === self::anyDev()) {
if ((string) $c2 === (string) $c || $c2 === Interval::anyDev()) {
$otherGroupMatches++;
}

Expand Down Expand Up @@ -242,8 +242,8 @@ private static function generateIntervals(ConstraintInterface $constraint, $stop
$borders = array();
foreach ($intervalGroups as $group) {
foreach ($group as $interval) {
$borders[] = array('version' => $interval['start']->getVersion(), 'operator' => $interval['start']->getOperator(), 'side' => 'start');
$borders[] = array('version' => $interval['end']->getVersion(), 'operator' => $interval['end']->getOperator(), 'side' => 'end');
$borders[] = array('version' => $interval->getStart()->getVersion(), 'operator' => $interval->getStart()->getOperator(), 'side' => 'start');
$borders[] = array('version' => $interval->getEnd()->getVersion(), 'operator' => $interval->getEnd()->getOperator(), 'side' => 'end');
}
}

Expand All @@ -262,44 +262,47 @@ private static function generateIntervals(ConstraintInterface $constraint, $stop
$index = 0;
$activationThreshold = $constraint->isConjunctive() ? \count($intervalGroups) : 1;
$active = false;
$start = null;
foreach ($borders as $border) {
if ($border['side'] === 'start') {
$activeIntervals++;
} else {
$activeIntervals--;
}
if (!$active && $activeIntervals >= $activationThreshold) {
$intervals[$index] = array('start' => new Constraint($border['operator'], $border['version']));
$start = new Constraint($border['operator'], $border['version']);
$active = true;
}
if ($active && $activeIntervals < $activationThreshold) {
$active = false;

// filter out invalid intervals like > x - <= x, or >= x - < x
if (
version_compare($intervals[$index]['start']->getVersion(), $border['version'], '=')
version_compare($start->getVersion(), $border['version'], '=')
&& (
($intervals[$index]['start']->getOperator() === '>' && $border['operator'] === '<=')
|| ($intervals[$index]['start']->getOperator() === '>=' && $border['operator'] === '<')
($start->getOperator() === '>' && $border['operator'] === '<=')
|| ($start->getOperator() === '>=' && $border['operator'] === '<')
)
) {
unset($intervals[$index]);
} else {
$intervals[$index]['end'] = new Constraint($border['operator'], $border['version']);
$intervals[$index] = new Interval($start, new Constraint($border['operator'], $border['version']));
$index++;

if ($stopOnFirstValidInterval) {
break;
}
}

$start = null;
}
}

return array('intervals' => $intervals, 'devConstraints' => $dev);
}

/**
* @phpstan-return array{'intervals': array<int, array{'start': Constraint, 'end': Constraint}>, 'devConstraints': Constraint[]}
* @phpstan-return array{'intervals': Interval[], 'devConstraints': Constraint[]}
*/
private static function generateSingleConstraintIntervals(Constraint $constraint)
{
Expand All @@ -309,64 +312,29 @@ private static function generateSingleConstraintIntervals(Constraint $constraint

// != dev-foo means any numeric version may match
if ($op === '!=') {
$intervals[] = array('start' => self::zero(), 'end' => self::positiveInfinity());
$intervals[] = new Interval(Interval::zero(), Interval::positiveInfinity());
}

return array('intervals' => $intervals, 'devConstraints' => array($constraint));
}

if ($op[0] === '>') { // > & >=
return array('intervals' => array(array('start' => $constraint, 'end' => self::positiveInfinity())), 'devConstraints' => array());
return array('intervals' => array(new Interval($constraint, Interval::positiveInfinity())), 'devConstraints' => array());
}
if ($op[0] === '<') { // < & <=
return array('intervals' => array(array('start' => self::zero(), 'end' => $constraint)), 'devConstraints' => array());
return array('intervals' => array(new Interval(Interval::zero(), $constraint)), 'devConstraints' => array());
}
if ($op === '!=') {
// convert !=x to intervals of 0 - <x && >x - +inf + dev*
return array('intervals' => array(
array('start' => self::zero(), 'end' => new Constraint('<', $constraint->getVersion())),
array('start' => new Constraint('>', $constraint->getVersion()), 'end' => self::positiveInfinity()),
), 'devConstraints' => array(self::anyDev()));
new Interval(Interval::zero(), new Constraint('<', $constraint->getVersion())),
new Interval(new Constraint('>', $constraint->getVersion()), Interval::positiveInfinity()),
), 'devConstraints' => array(Interval::anyDev()));
}

// convert ==x to an interval of >=x - <=x
return array('intervals' => array(
array('start' => new Constraint('>=', $constraint->getVersion()), 'end' => new Constraint('<=', $constraint->getVersion())),
new Interval(new Constraint('>=', $constraint->getVersion()), new Constraint('<=', $constraint->getVersion())),
), 'devConstraints' => array());
}

public static function zero()
{
static $zero;

if (null === $zero) {
$zero = new Constraint('>=', '0.0.0.0-dev');
}

return $zero;
}

public static function positiveInfinity()
{
static $positiveInfinity;

if (null === $positiveInfinity) {
$positiveInfinity = new Constraint('<', PHP_INT_MAX.'.0.0.0');
}

return $positiveInfinity;
}

public static function anyDev()
{
static $anyDev;

if (null === $anyDev) {
// this ideally should be an EmptyConstraint but the code expects Constraint instances so
// this makes it work with less workarounds/checks above
$anyDev = new Constraint('==', 'dev*');
}

return $anyDev;
}
}
3 changes: 3 additions & 0 deletions tests/IntervalsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ public function testGetIntervals($expected, $constraint)
$result = Intervals::get($constraint);
if (is_array($result)) {
array_walk_recursive($result, function (&$c) {
if ($c instanceof Interval) {
$c = array('start' => (string) $c->getStart(), 'end' => (string) $c->getEnd());
}
if ($c instanceof ConstraintInterface) {
$c = (string) $c;
}
Expand Down

0 comments on commit c3e5429

Please sign in to comment.