Skip to content

Commit

Permalink
bug #5 Make VersionValidator produce better suggestions (sstok)
Browse files Browse the repository at this point in the history
This PR was merged into the 1.0-dev branch.
labels: bc-break

Discussion
----------

The Validator had no tests at all (closes #3 ) and the algorithm was horrible flawed, suggestions could include already existing versions and predictions for a new major or minor release while this was not logically expected.

This pull request changes the validator completely, by using a more descriptive name and separating concerns of validating and getting possible versions.

Note: The suggested versions might be different than expected, this algorithm is highly opinionated and tries to enforce a continues versioning list.

Commits
-------

fb64dff Make VersionValidator produce better suggestions
  • Loading branch information
sstok authored Nov 14, 2018
2 parents f3092e6 + fb64dff commit a1b8fbd
Show file tree
Hide file tree
Showing 8 changed files with 352 additions and 217 deletions.
68 changes: 39 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
Rollerworks Semver Component
============================

Semantic Versioning helper library.
Validation, incrementing (get next possible version(s)).
A small Semantic Versioning helper library.

Validation Continues Versions. Finding next possible version version increments.

Requirements
------------

You need at least PHP 7.0
You need at least PHP 7.1

Installation
------------
Expand Down Expand Up @@ -63,34 +64,43 @@ $newVersion = $version->increase('stable'); // v1.4.0
// Version validation
// ... //

$tags = [
'0.1.0',
'v1.0.0-beta1',
'v1.0.0-beta2',
'v1.0.0-beta6',
'v1.0.0-beta7',
'1.0.0',
'v1.0.1',
'v1.1.0',
'v2.0.0',
'v3.5-beta1',
$existingVersions = [
Version::fromString('0.1.0'),
Version::fromString('v1.0.0-beta1'),
Version::fromString('v1.0.0-beta2'),
Version::fromString('v1.0.0-beta6'),
Version::fromString('v1.0.0-beta7'),
Version::fromString('1.0.0'),
Version::fromString('v1.0.1'),
Version::fromString('v1.1.0'),
Version::fromString('v2.0.0'),
Version::fromString('v3.5-beta1'),
];

// Return an array with major version as key, and the highest possible
// version for that major as Version object
$versions = VersionsValidator::getHighestVersions($tags);

// [
// 0 => Version::fromString('0.1.0'),
// 1 => Version::fromString('1.1.0'),
// 2 => Version::fromString('2.0.0'),
// 3 => Version::fromString('3.5.0-beta1'),
// ]

// $possibleVersions is a returning reference holding a list of acceptable versions
VersionsValidator::isVersionContinues($versions, Version::fromString('v0.2.0'), $possibleVersions); // true
VersionsValidator::isVersionContinues($versions, Version::fromString('v0.1.1'), $possibleVersions); // true
VersionsValidator::isVersionContinues($versions, Version::fromString('v1.3.2'), $possibleVersions); // false
$validator = new ContinuesVersionsValidator(...$existingVersions); // Expects the versions as a variadic arguments
//$validator = new ContinuesVersionsValidator(); // No existing versions

VersionsValidator::isVersionContinues(Version::fromString('v1.1.1')); // true
VersionsValidator::isVersionContinues(Version::fromString('1.0.2')); // true
VersionsValidator::isVersionContinues(Version::fromString('1.1.1.')); // true
VersionsValidator::isVersionContinues(Version::fromString('2.0.1.')); // true
VersionsValidator::isVersionContinues(Version::fromString('3.5.0-beta2')); // true
VersionsValidator::isVersionContinues(Version::fromString('3.5.0')); // true

// A new minor or major version is not considered acceptable when there are already higher
// versions. Only patch releases are accepted then.
VersionsValidator::isVersionContinues(Version::fromString('0.2.0')); // false
VersionsValidator::isVersionContinues(Version::fromString('v1.0.0-beta8')); // false
VersionsValidator::isVersionContinues(Version::fromString('v1.2')); // false
VersionsValidator::isVersionContinues(Version::fromString('v2.1')); // false
VersionsValidator::isVersionContinues(Version::fromString('v3.5-alpha1')); // false
VersionsValidator::isVersionContinues(Version::fromString('v3.5-beta3')); // false
VersionsValidator::isVersionContinues(Version::fromString('v3.6')); // false

// A list of possible versions with respect to the major.minor bounds of any existing version
// For higher major.minor versions then validated only suggests a patch release, otherwise
// all possible increments till the next stable major are suggested.
$possibleVersions = $validator->getPossibleVersions();
```

Versioning
Expand Down
27 changes: 27 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,30 @@ UPGRADE

* The method `increase` of `Version` is renamed to `getNextIncreaseOf`.

* The `VersionValidator` has been renamed to `ContinuesVersionsValidator`.

* The `ContinuesVersionsValidator` has api changed to separate the
validation from providing suggestions.

Before:

```php
VersionsValidator::isVersionContinues($versions, Version::fromString('v0.2.0'), $possibleVersions);
```

After:

```php
$validator = new ContinuesVersionsValidator(...$existingVersions); // Expects the versions as a variadic arguments

$result = ContinuesVersionsValidator::isContinues(Version::fromString('v0.2.0'));
$possibleVersions = $validator->getPossibleVersions(); // Must be called after isContinues(), otherwise empty
```

**Note:** The suggested versions did not take existing versions into account.

Now, instead if a newer major or minor version already exists it only allows
a patch release for the bounded minor version. If both 1.1 and 2.0 exist then
1.2 is no longer suggested, nor considered an acceptable increment.

Otherwise all possible suggestions are accepted.
3 changes: 1 addition & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@
}
],
"require": {
"php": "^7.1",
"composer/semver": "^1.4.2"
"php": "^7.1"
},
"require-dev": {
"phpunit/phpunit": "^7.3.2",
Expand Down
136 changes: 136 additions & 0 deletions src/ContinuesVersionsValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<?php

declare(strict_types=1);

/*
* This file is part of the Rollerworks Semver package.
*
* (c) Sebastiaan Stok <[email protected]>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/

namespace Rollerworks\Component\Version;

use function count;

final class ContinuesVersionsValidator
{
/** @var Version[] */
private $versions = [];

/** @var Version[] */
private $possibleVersions = [];

/** @var array<int, Version[]> */
private $resolveVersions = [];

public function __construct(Version ...$versions)
{
$this->versions = $versions;
}

public function isContinues(Version $new): bool
{
if (count($this->versions) === 0) {
$this->possibleVersions = [
Version::fromString('0.1.0'),
Version::fromString('1.0.0-ALPHA1'),
Version::fromString('1.0.0-BETA1'),
Version::fromString('1.0.0'),
];
} else {
$this->computePossibleVersions($new);
}

foreach ($this->possibleVersions as $possibleVersion) {
if ($possibleVersion->equalTo($new)) {
return true;
}
}

return false;
}

/**
* @return Version[]
*/
public function getPossibleVersions(): array
{
return $this->possibleVersions;
}

private function computePossibleVersions(Version $new): void
{
$this->arrangeExistingVersions();

$major = $new->major;
$minor = $new->minor;

if (!isset($this->resolveVersions[$major])) {
$this->computePossibleVersionsFromLastExisting();

return;
}

if (!isset($this->resolveVersions[$major][$minor])) {
$minor = $this->getLastArrayIndex($this->resolveVersions[$major]);
}

$this->computePossibleVersionsFromMinor($major, $minor);
}

private function arrangeExistingVersions(): void
{
usort($this->versions, function (Version $a, Version $b) {
return version_compare(strtolower($a->full), strtolower($b->full), '<') ? -1 : 1;
});

$resolvedVersions = [];

foreach ($this->versions as $version) {
$resolvedVersions[$version->major][$version->minor] = $version;
}

$this->resolveVersions = $resolvedVersions;
}

private function computePossibleVersionsFromLastExisting(): void
{
$major = $this->getLastArrayIndex($this->resolveVersions);
$minor = $this->getLastArrayIndex($this->resolveVersions[$major]);

/** @var Version $version */
$version = $this->resolveVersions[$major][$minor];
$this->possibleVersions = $version->getNextVersionCandidates();
}

private function getLastArrayIndex(array $array)
{
end($array);

return key($array);
}

private function hasNewerVersionsAfter(int $major, int $minor): bool
{
return $this->getLastArrayIndex($this->resolveVersions) > $major ||
$this->getLastArrayIndex($this->resolveVersions[$major]) > $minor;
}

private function computePossibleVersionsFromMinor(int $major, int $minor): void
{
/** @var Version $version */
$version = $this->resolveVersions[$major][$minor];

if ($this->hasNewerVersionsAfter($major, $minor)) {
$this->possibleVersions = [$version->getNextIncreaseOf('patch')];

return;
}

$versionCandidates = $version->getNextVersionCandidates();
$this->possibleVersions = $versionCandidates;
}
}
11 changes: 11 additions & 0 deletions src/Version.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,22 @@ final class Version
*/
public const VERSION_REGEX = '(?P<major>\d++)\.(?P<minor>\d++)(?:\.(?P<patch>\d++))?(?:[-.]?(?P<stability>beta|RC|alpha|stable)(?:[.-]?(?P<metaver>\d+))?)?';

/** @var int */
public $major;

/** @var int */
public $minor;

/** @var int */
public $patch;

/** @var int */
public $stability;

/** @var int */
public $metaver;

/** @var string */
public $full;

/**
Expand Down
102 changes: 0 additions & 102 deletions src/VersionsValidator.php

This file was deleted.

Loading

0 comments on commit a1b8fbd

Please sign in to comment.