├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── composer.json └── src ├── BuildMetaData.php ├── PreReleaseSuffix.php ├── Version.php ├── VersionConstraintParser.php ├── VersionConstraintValue.php ├── VersionNumber.php ├── constraints ├── AbstractVersionConstraint.php ├── AndVersionConstraintGroup.php ├── AnyVersionConstraint.php ├── ExactVersionConstraint.php ├── GreaterThanOrEqualToVersionConstraint.php ├── OrVersionConstraintGroup.php ├── SpecificMajorAndMinorVersionConstraint.php ├── SpecificMajorVersionConstraint.php └── VersionConstraint.php └── exceptions ├── Exception.php ├── InvalidPreReleaseSuffixException.php ├── InvalidVersionException.php ├── NoBuildMetaDataException.php ├── NoPreReleaseSuffixException.php └── UnsupportedVersionConstraintException.php /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [theseer] 4 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: "CI" 2 | 3 | on: 4 | pull_request: null 5 | push: 6 | branches: 7 | - "master" 8 | 9 | jobs: 10 | qa: 11 | name: "QA" 12 | 13 | runs-on: "ubuntu-latest" 14 | 15 | strategy: 16 | matrix: 17 | php-version: 18 | - "8.0" 19 | 20 | steps: 21 | - name: "Checkout" 22 | uses: "actions/checkout@v3.5.2" 23 | 24 | - name: "Set up PHP" 25 | uses: "shivammathur/setup-php@2.25.1" 26 | with: 27 | coverage: "none" 28 | php-version: "${{ matrix.php-version }}" 29 | tools: "phive" 30 | 31 | - name: "Install dependencies with phive" 32 | env: 33 | GITHUB_AUTH_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 34 | run: "ant install-tools" 35 | 36 | - name: "Run php-cs-fixer" 37 | run: "ant php-cs-fixer" 38 | 39 | - name: "Run psalm" 40 | run: "ant psalm" 41 | 42 | tests: 43 | name: "Tests" 44 | 45 | runs-on: "ubuntu-latest" 46 | 47 | strategy: 48 | fail-fast: false 49 | 50 | matrix: 51 | php-version: 52 | - "7.3" 53 | - "7.4" 54 | - "8.0" 55 | - "8.1" 56 | - "8.2" 57 | 58 | steps: 59 | - name: "Checkout" 60 | uses: "actions/checkout@v3.5.2" 61 | 62 | - name: "Set up PHP" 63 | uses: "shivammathur/setup-php@2.25.1" 64 | env: 65 | COMPOSER_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 66 | with: 67 | coverage: "pcov" 68 | extensions: "${{ env.extensions }}" 69 | ini-values: "display_errors=On, error_reporting=-1, memory_limit=2G" 70 | php-version: "${{ matrix.php-version }}" 71 | tools: "phive" 72 | 73 | - name: "Install dependencies with phive" 74 | env: 75 | GITHUB_AUTH_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 76 | run: "ant install-tools" 77 | 78 | - name: "Run PHPUnit with code coverage" 79 | run: "ant test-with-code-coverage" 80 | 81 | - name: "Send code coverage report to codecov.io" 82 | uses: "codecov/codecov-action@v3.1.4" 83 | with: 84 | files: "build/logs/clover.xml" 85 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to phar-io/version are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. 4 | 5 | ## [3.3.0] - 2024-??-?? 6 | 7 | ### Fixed 8 | 9 | - [#35](https://github.com/phar-io/version/issues/35): major version only not supported, e.g. ^6 10 | 11 | 12 | ## [3.2.1] - 2022-02-21 13 | 14 | ### Fixed 15 | 16 | - Have ExactVersionConstraint honor build metadata (added in 3.2.0) 17 | 18 | 19 | ## [3.2.0] - 2022-02-21 20 | 21 | ### Added 22 | 23 | - Build metadata is now supported and considered for equality checks only 24 | 25 | 26 | ## [3.1.1] - 2022-02-07 27 | 28 | ### Fixed 29 | 30 | - [#28](https://github.com/phar-io/version/issues/28): `VersionConstraintParser` does not support logical OR represented by single pipe (|) (Thanks @llaville) 31 | 32 | 33 | ## [3.1.0] - 2021-02-23 34 | 35 | ### Changed 36 | 37 | - Internal Refactoring 38 | - More scalar types 39 | 40 | ### Added 41 | 42 | - [#24](https://github.com/phar-io/version/issues/24): `Version::getOriginalString()` added (Thanks @addshore) 43 | - Version constraints using the caret operator (`^`) now honor pre-1.0 releases, e.g. `^0.3` translates to `0.3.*`) 44 | - Various integration tests for version constraint processing 45 | 46 | ### Fixed 47 | 48 | - [#23](https://github.com/phar-io/version/pull/23): Tilde operator without patch level 49 | 50 | 51 | 52 | ## [3.0.4] - 14.12.2020 53 | 54 | ### Fixed 55 | 56 | - [#22](https://github.com/phar-io/version/pull/22): make dev suffix rank works for uppercase too 57 | 58 | ## [3.0.3] - 30.11.2020 59 | 60 | ### Added 61 | 62 | - Comparator method `Version::equals()` added 63 | 64 | 65 | ## [3.0.2] - 27.06.2020 66 | 67 | This release now supports PHP 7.2+ and PHP ^8.0. No other changes included. 68 | 69 | 70 | ## [3.0.1] - 09.05.2020 71 | 72 | __Potential BC Break Notice:__ 73 | `Version::getVersionString()` no longer returns `v` prefixes in case the "input" 74 | string contained one. These are not part of the semver specs 75 | (see https://semver.org/#is-v123-a-semantic-version) and get stripped out. 76 | As of Version 3.1.0 `Version::getOriginalString()` can be used to still 77 | retrieve it as given. 78 | 79 | ### Changed 80 | 81 | - Internal Refactoring 82 | - More scalar types 83 | 84 | ### Fixed 85 | 86 | - Fixed Constraint processing Regression for ^1.2 and ~1.2 87 | 88 | 89 | ## [3.0.0] - 05.05.2020 90 | 91 | ### Changed 92 | 93 | - Require PHP 7.2+ 94 | - All code now uses strict mode 95 | - Scalar types have been added as needed 96 | 97 | ### Added 98 | 99 | - The technically invalid format using 'v' prefix ("v1.2.3") is now properly supported 100 | 101 | 102 | ## [2.0.1] - 08.07.2018 103 | 104 | ### Fixed 105 | 106 | - Versions without a pre-release suffix are now always considered greater 107 | than versions with a pre-release suffix. Example: `3.0.0 > 3.0.0-alpha.1` 108 | 109 | 110 | ## [2.0.0] - 23.06.2018 111 | 112 | Changes to public API: 113 | 114 | - `PreReleaseSuffix::construct()`: optional parameter `$number` removed 115 | - `PreReleaseSuffix::isGreaterThan()`: introduced 116 | - `Version::hasPreReleaseSuffix()`: introduced 117 | 118 | ### Added 119 | 120 | - [#11](https://github.com/phar-io/version/issues/11): Added support for pre-release version suffixes. Supported values are: 121 | - `dev` 122 | - `beta` (also abbreviated form `b`) 123 | - `rc` 124 | - `alpha` (also abbreviated form `a`) 125 | - `patch` (also abbreviated form `p`) 126 | 127 | All values can be followed by a number, e.g. `beta3`. 128 | 129 | When comparing versions, the pre-release suffix is taken into account. Example: 130 | `1.5.0 > 1.5.0-beta1 > 1.5.0-alpha3 > 1.5.0-alpha2 > 1.5.0-dev11` 131 | 132 | ### Changed 133 | 134 | - reorganized the source directories 135 | 136 | ### Fixed 137 | 138 | - [#10](https://github.com/phar-io/version/issues/10): Version numbers containing 139 | a numeric suffix as seen in Debian packages are now supported. 140 | 141 | 142 | [3.3.0]: https://github.com/phar-io/version/compare/3.2.1...3.3.0 143 | [3.2.1]: https://github.com/phar-io/version/compare/3.2.1...3.2.1 144 | [3.2.0]: https://github.com/phar-io/version/compare/3.1.1...3.2.0 145 | [3.1.1]: https://github.com/phar-io/version/compare/3.1.0...3.1.1 146 | [3.1.0]: https://github.com/phar-io/version/compare/3.0.4...3.1.0 147 | [3.0.4]: https://github.com/phar-io/version/compare/3.0.3...3.0.4 148 | [3.0.3]: https://github.com/phar-io/version/compare/3.0.2...3.0.3 149 | [3.0.2]: https://github.com/phar-io/version/compare/3.0.1...3.0.2 150 | [3.0.1]: https://github.com/phar-io/version/compare/3.0.0...3.0.1 151 | [3.0.0]: https://github.com/phar-io/version/compare/2.0.1...3.0.0 152 | [2.0.1]: https://github.com/phar-io/version/compare/2.0.0...2.0.1 153 | [2.0.0]: https://github.com/phar-io/version/compare/1.0.1...2.0.0 154 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-2017 Arne Blankerts , Sebastian Heuer and contributors 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the copyright holder nor the names of contributors 15 | may be used to endorse or promote products derived from this software 16 | without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, 20 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 21 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS 22 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 23 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | POSSIBILITY OF SUCH DAMAGE. 29 | 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Version 2 | 3 | Library for handling version information and constraints 4 | 5 | [![CI](https://github.com/phar-io/version/actions/workflows/ci.yml/badge.svg)](https://github.com/phar-io/version/actions/workflows/ci.yml) 6 | 7 | ## Installation 8 | 9 | You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/): 10 | 11 | composer require phar-io/version 12 | 13 | If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency: 14 | 15 | composer require --dev phar-io/version 16 | 17 | ## Version constraints 18 | 19 | A Version constraint describes a range of versions or a discrete version number. The format of version numbers follows the schema of [semantic versioning](http://semver.org): `..`. A constraint might contain an operator that describes the range. 20 | 21 | Beside the typical mathematical operators like `<=`, `>=`, there are two special operators: 22 | 23 | *Caret operator*: `^1.0` 24 | can be written as `>=1.0.0 <2.0.0` and read as »every Version within major version `1`«. 25 | 26 | *Tilde operator*: `~1.0.0` 27 | can be written as `>=1.0.0 <1.1.0` and read as »every version within minor version `1.1`. The behavior of tilde operator depends on whether a patch level version is provided or not. If no patch level is provided, tilde operator behaves like the caret operator: `~1.0` is identical to `^1.0`. 28 | 29 | ## Usage examples 30 | 31 | Parsing version constraints and check discrete versions for compliance: 32 | 33 | ```php 34 | 35 | use PharIo\Version\Version; 36 | use PharIo\Version\VersionConstraintParser; 37 | 38 | $parser = new VersionConstraintParser(); 39 | $caret_constraint = $parser->parse( '^7.0' ); 40 | 41 | $caret_constraint->complies( new Version( '7.0.17' ) ); // true 42 | $caret_constraint->complies( new Version( '7.1.0' ) ); // true 43 | $caret_constraint->complies( new Version( '6.4.34' ) ); // false 44 | 45 | $tilde_constraint = $parser->parse( '~1.1.0' ); 46 | 47 | $tilde_constraint->complies( new Version( '1.1.4' ) ); // true 48 | $tilde_constraint->complies( new Version( '1.2.0' ) ); // false 49 | ``` 50 | 51 | As of version 2.0.0, pre-release labels are supported and taken into account when comparing versions: 52 | 53 | ```php 54 | 55 | $leftVersion = new PharIo\Version\Version('3.0.0-alpha.1'); 56 | $rightVersion = new PharIo\Version\Version('3.0.0-alpha.2'); 57 | 58 | $leftVersion->isGreaterThan($rightVersion); // false 59 | $rightVersion->isGreaterThan($leftVersion); // true 60 | 61 | ``` 62 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phar-io/version", 3 | "description": "Library for handling version information and constraints", 4 | "license": "BSD-3-Clause", 5 | "authors": [ 6 | { 7 | "name": "Arne Blankerts", 8 | "email": "arne@blankerts.de", 9 | "role": "Developer" 10 | }, 11 | { 12 | "name": "Sebastian Heuer", 13 | "email": "sebastian@phpeople.de", 14 | "role": "Developer" 15 | }, 16 | { 17 | "name": "Sebastian Bergmann", 18 | "email": "sebastian@phpunit.de", 19 | "role": "Developer" 20 | } 21 | ], 22 | "support": { 23 | "issues": "https://github.com/phar-io/version/issues" 24 | }, 25 | "require": { 26 | "php": "^7.2 || ^8.0" 27 | }, 28 | "autoload": { 29 | "classmap": [ 30 | "src/" 31 | ] 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /src/BuildMetaData.php: -------------------------------------------------------------------------------- 1 | , Sebastian Heuer , Sebastian Bergmann 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PharIo\Version; 11 | 12 | class BuildMetaData { 13 | 14 | /** @var string */ 15 | private $value; 16 | 17 | public function __construct(string $value) { 18 | $this->value = $value; 19 | } 20 | 21 | public function asString(): string { 22 | return $this->value; 23 | } 24 | 25 | public function equals(BuildMetaData $other): bool { 26 | return $this->asString() === $other->asString(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/PreReleaseSuffix.php: -------------------------------------------------------------------------------- 1 | 0, 7 | 'a' => 1, 8 | 'alpha' => 1, 9 | 'b' => 2, 10 | 'beta' => 2, 11 | 'rc' => 3, 12 | 'p' => 4, 13 | 'pl' => 4, 14 | 'patch' => 4, 15 | ]; 16 | 17 | /** @var string */ 18 | private $value; 19 | 20 | /** @var int */ 21 | private $valueScore; 22 | 23 | /** @var int */ 24 | private $number = 0; 25 | 26 | /** @var string */ 27 | private $full; 28 | 29 | /** 30 | * @throws InvalidPreReleaseSuffixException 31 | */ 32 | public function __construct(string $value) { 33 | $this->parseValue($value); 34 | } 35 | 36 | public function asString(): string { 37 | return $this->full; 38 | } 39 | 40 | public function getValue(): string { 41 | return $this->value; 42 | } 43 | 44 | public function getNumber(): ?int { 45 | return $this->number; 46 | } 47 | 48 | public function isGreaterThan(PreReleaseSuffix $suffix): bool { 49 | if ($this->valueScore > $suffix->valueScore) { 50 | return true; 51 | } 52 | 53 | if ($this->valueScore < $suffix->valueScore) { 54 | return false; 55 | } 56 | 57 | return $this->getNumber() > $suffix->getNumber(); 58 | } 59 | 60 | private function mapValueToScore(string $value): int { 61 | $value = \strtolower($value); 62 | 63 | return self::valueScoreMap[$value]; 64 | } 65 | 66 | private function parseValue(string $value): void { 67 | $regex = '/-?((dev|beta|b|rc|alpha|a|patch|p|pl)\.?(\d*)).*$/i'; 68 | 69 | if (\preg_match($regex, $value, $matches) !== 1) { 70 | throw new InvalidPreReleaseSuffixException(\sprintf('Invalid label %s', $value)); 71 | } 72 | 73 | $this->full = $matches[1]; 74 | $this->value = $matches[2]; 75 | 76 | if ($matches[3] !== '') { 77 | $this->number = (int)$matches[3]; 78 | } 79 | 80 | $this->valueScore = $this->mapValueToScore($matches[2]); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Version.php: -------------------------------------------------------------------------------- 1 | , Sebastian Heuer , Sebastian Bergmann 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PharIo\Version; 11 | 12 | class Version { 13 | /** @var string */ 14 | private $originalVersionString; 15 | 16 | /** @var VersionNumber */ 17 | private $major; 18 | 19 | /** @var VersionNumber */ 20 | private $minor; 21 | 22 | /** @var VersionNumber */ 23 | private $patch; 24 | 25 | /** @var null|PreReleaseSuffix */ 26 | private $preReleaseSuffix; 27 | 28 | /** @var null|BuildMetaData */ 29 | private $buildMetadata; 30 | 31 | public function __construct(string $versionString) { 32 | $this->ensureVersionStringIsValid($versionString); 33 | $this->originalVersionString = $versionString; 34 | } 35 | 36 | /** 37 | * @throws NoPreReleaseSuffixException 38 | */ 39 | public function getPreReleaseSuffix(): PreReleaseSuffix { 40 | if ($this->preReleaseSuffix === null) { 41 | throw new NoPreReleaseSuffixException('No pre-release suffix set'); 42 | } 43 | 44 | return $this->preReleaseSuffix; 45 | } 46 | 47 | public function getOriginalString(): string { 48 | return $this->originalVersionString; 49 | } 50 | 51 | public function getVersionString(): string { 52 | $str = \sprintf( 53 | '%d.%d.%d', 54 | $this->getMajor()->getValue() ?? 0, 55 | $this->getMinor()->getValue() ?? 0, 56 | $this->getPatch()->getValue() ?? 0 57 | ); 58 | 59 | if (!$this->hasPreReleaseSuffix()) { 60 | return $str; 61 | } 62 | 63 | return $str . '-' . $this->getPreReleaseSuffix()->asString(); 64 | } 65 | 66 | public function hasPreReleaseSuffix(): bool { 67 | return $this->preReleaseSuffix !== null; 68 | } 69 | 70 | public function equals(Version $other): bool { 71 | if ($this->getVersionString() !== $other->getVersionString()) { 72 | return false; 73 | } 74 | 75 | if ($this->hasBuildMetaData() !== $other->hasBuildMetaData()) { 76 | return false; 77 | } 78 | 79 | if ($this->hasBuildMetaData() && $other->hasBuildMetaData() && 80 | !$this->getBuildMetaData()->equals($other->getBuildMetaData())) { 81 | return false; 82 | } 83 | 84 | return true; 85 | } 86 | 87 | public function isGreaterThan(Version $version): bool { 88 | if ($version->getMajor()->getValue() > $this->getMajor()->getValue()) { 89 | return false; 90 | } 91 | 92 | if ($version->getMajor()->getValue() < $this->getMajor()->getValue()) { 93 | return true; 94 | } 95 | 96 | if ($version->getMinor()->getValue() > $this->getMinor()->getValue()) { 97 | return false; 98 | } 99 | 100 | if ($version->getMinor()->getValue() < $this->getMinor()->getValue()) { 101 | return true; 102 | } 103 | 104 | if ($version->getPatch()->getValue() > $this->getPatch()->getValue()) { 105 | return false; 106 | } 107 | 108 | if ($version->getPatch()->getValue() < $this->getPatch()->getValue()) { 109 | return true; 110 | } 111 | 112 | if (!$version->hasPreReleaseSuffix() && !$this->hasPreReleaseSuffix()) { 113 | return false; 114 | } 115 | 116 | if ($version->hasPreReleaseSuffix() && !$this->hasPreReleaseSuffix()) { 117 | return true; 118 | } 119 | 120 | if (!$version->hasPreReleaseSuffix() && $this->hasPreReleaseSuffix()) { 121 | return false; 122 | } 123 | 124 | return $this->getPreReleaseSuffix()->isGreaterThan($version->getPreReleaseSuffix()); 125 | } 126 | 127 | public function getMajor(): VersionNumber { 128 | return $this->major; 129 | } 130 | 131 | public function getMinor(): VersionNumber { 132 | return $this->minor; 133 | } 134 | 135 | public function getPatch(): VersionNumber { 136 | return $this->patch; 137 | } 138 | 139 | /** 140 | * @psalm-assert-if-true BuildMetaData $this->buildMetadata 141 | * @psalm-assert-if-true BuildMetaData $this->getBuildMetaData() 142 | */ 143 | public function hasBuildMetaData(): bool { 144 | return $this->buildMetadata !== null; 145 | } 146 | 147 | /** 148 | * @throws NoBuildMetaDataException 149 | */ 150 | public function getBuildMetaData(): BuildMetaData { 151 | if (!$this->hasBuildMetaData()) { 152 | throw new NoBuildMetaDataException('No build metadata set'); 153 | } 154 | 155 | return $this->buildMetadata; 156 | } 157 | 158 | /** 159 | * @param string[] $matches 160 | * 161 | * @throws InvalidPreReleaseSuffixException 162 | */ 163 | private function parseVersion(array $matches): void { 164 | $this->major = new VersionNumber((int)$matches['Major']); 165 | $this->minor = isset($matches['Minor']) ? new VersionNumber((int)$matches['Minor']) : new VersionNumber(0); 166 | $this->patch = isset($matches['Patch']) ? new VersionNumber((int)$matches['Patch']) : new VersionNumber(0); 167 | 168 | if (isset($matches['PreReleaseSuffix']) && $matches['PreReleaseSuffix'] !== '') { 169 | $this->preReleaseSuffix = new PreReleaseSuffix($matches['PreReleaseSuffix']); 170 | } 171 | 172 | if (isset($matches['BuildMetadata'])) { 173 | $this->buildMetadata = new BuildMetaData($matches['BuildMetadata']); 174 | } 175 | } 176 | 177 | /** 178 | * @param string $version 179 | * 180 | * @throws InvalidVersionException 181 | */ 182 | private function ensureVersionStringIsValid($version): void { 183 | $regex = '/^v? 184 | (?P0|[1-9]\d*) 185 | (\\. 186 | (?P0|[1-9]\d*) 187 | )? 188 | (\\. 189 | (?P0|[1-9]\d*) 190 | )? 191 | (?: 192 | - 193 | (?(?:(dev|beta|b|rc|alpha|a|patch|p|pl)\.?\d*)) 194 | )? 195 | (?: 196 | \\+ 197 | (?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-@]+)*) 198 | )? 199 | $/xi'; 200 | 201 | if (\preg_match($regex, $version, $matches) !== 1) { 202 | throw new InvalidVersionException( 203 | \sprintf("Version string '%s' does not follow SemVer semantics", $version) 204 | ); 205 | } 206 | 207 | $this->parseVersion($matches); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/VersionConstraintParser.php: -------------------------------------------------------------------------------- 1 | , Sebastian Heuer , Sebastian Bergmann 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PharIo\Version; 11 | 12 | class VersionConstraintParser { 13 | /** 14 | * @throws UnsupportedVersionConstraintException 15 | */ 16 | public function parse(string $value): VersionConstraint { 17 | if (\strpos($value, '|') !== false) { 18 | return $this->handleOrGroup($value); 19 | } 20 | 21 | if (!\preg_match('/^[\^~*]?v?[\d.*]+(?:-.*)?$/i', $value)) { 22 | throw new UnsupportedVersionConstraintException( 23 | \sprintf('Version constraint %s is not supported.', $value) 24 | ); 25 | } 26 | 27 | switch ($value[0]) { 28 | case '~': 29 | return $this->handleTildeOperator($value); 30 | case '^': 31 | return $this->handleCaretOperator($value); 32 | } 33 | 34 | $constraint = new VersionConstraintValue($value); 35 | 36 | if ($constraint->getMajor()->isAny()) { 37 | return new AnyVersionConstraint(); 38 | } 39 | 40 | if ($constraint->getMinor()->isAny()) { 41 | return new SpecificMajorVersionConstraint( 42 | $constraint->getVersionString(), 43 | $constraint->getMajor()->getValue() ?? 0 44 | ); 45 | } 46 | 47 | if ($constraint->getPatch()->isAny()) { 48 | return new SpecificMajorAndMinorVersionConstraint( 49 | $constraint->getVersionString(), 50 | $constraint->getMajor()->getValue() ?? 0, 51 | $constraint->getMinor()->getValue() ?? 0 52 | ); 53 | } 54 | 55 | return new ExactVersionConstraint($constraint->getVersionString()); 56 | } 57 | 58 | private function handleOrGroup(string $value): OrVersionConstraintGroup { 59 | $constraints = []; 60 | 61 | foreach (\preg_split('{\s*\|\|?\s*}', \trim($value)) as $groupSegment) { 62 | $constraints[] = $this->parse(\trim($groupSegment)); 63 | } 64 | 65 | return new OrVersionConstraintGroup($value, $constraints); 66 | } 67 | 68 | private function handleTildeOperator(string $value): AndVersionConstraintGroup { 69 | $constraintValue = new VersionConstraintValue(\substr($value, 1)); 70 | 71 | if ($constraintValue->getPatch()->isAny()) { 72 | return $this->handleCaretOperator($value); 73 | } 74 | 75 | $constraints = [ 76 | new GreaterThanOrEqualToVersionConstraint( 77 | $value, 78 | new Version(\substr($value, 1)) 79 | ), 80 | new SpecificMajorAndMinorVersionConstraint( 81 | $value, 82 | $constraintValue->getMajor()->getValue() ?? 0, 83 | $constraintValue->getMinor()->getValue() ?? 0 84 | ) 85 | ]; 86 | 87 | return new AndVersionConstraintGroup($value, $constraints); 88 | } 89 | 90 | private function handleCaretOperator(string $value): AndVersionConstraintGroup { 91 | $constraintValue = new VersionConstraintValue(\substr($value, 1)); 92 | 93 | $constraints = [ 94 | new GreaterThanOrEqualToVersionConstraint($value, new Version(\substr($value, 1))) 95 | ]; 96 | 97 | if ($constraintValue->getMajor()->getValue() === 0) { 98 | $constraints[] = new SpecificMajorAndMinorVersionConstraint( 99 | $value, 100 | $constraintValue->getMajor()->getValue() ?? 0, 101 | $constraintValue->getMinor()->getValue() ?? 0 102 | ); 103 | } else { 104 | $constraints[] = new SpecificMajorVersionConstraint( 105 | $value, 106 | $constraintValue->getMajor()->getValue() ?? 0 107 | ); 108 | } 109 | 110 | return new AndVersionConstraintGroup( 111 | $value, 112 | $constraints 113 | ); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/VersionConstraintValue.php: -------------------------------------------------------------------------------- 1 | versionString = $versionString; 25 | 26 | $this->parseVersion($versionString); 27 | } 28 | 29 | public function getLabel(): string { 30 | return $this->label; 31 | } 32 | 33 | public function getBuildMetaData(): string { 34 | return $this->buildMetaData; 35 | } 36 | 37 | public function getVersionString(): string { 38 | return $this->versionString; 39 | } 40 | 41 | public function getMajor(): VersionNumber { 42 | return $this->major; 43 | } 44 | 45 | public function getMinor(): VersionNumber { 46 | return $this->minor; 47 | } 48 | 49 | public function getPatch(): VersionNumber { 50 | return $this->patch; 51 | } 52 | 53 | private function parseVersion(string $versionString): void { 54 | $this->extractBuildMetaData($versionString); 55 | $this->extractLabel($versionString); 56 | $this->stripPotentialVPrefix($versionString); 57 | 58 | $versionSegments = \explode('.', $versionString); 59 | $this->major = new VersionNumber(\is_numeric($versionSegments[0]) ? (int)$versionSegments[0] : null); 60 | 61 | $minorValue = isset($versionSegments[1]) && \is_numeric($versionSegments[1]) ? (int)$versionSegments[1] : null; 62 | $patchValue = isset($versionSegments[2]) && \is_numeric($versionSegments[2]) ? (int)$versionSegments[2] : null; 63 | 64 | $this->minor = new VersionNumber($minorValue); 65 | $this->patch = new VersionNumber($patchValue); 66 | } 67 | 68 | private function extractBuildMetaData(string &$versionString): void { 69 | if (\preg_match('/\+(.*)/', $versionString, $matches) === 1) { 70 | $this->buildMetaData = $matches[1]; 71 | $versionString = \str_replace($matches[0], '', $versionString); 72 | } 73 | } 74 | 75 | private function extractLabel(string &$versionString): void { 76 | if (\preg_match('/-(.*)/', $versionString, $matches) === 1) { 77 | $this->label = $matches[1]; 78 | $versionString = \str_replace($matches[0], '', $versionString); 79 | } 80 | } 81 | 82 | private function stripPotentialVPrefix(string &$versionString): void { 83 | if ($versionString[0] !== 'v') { 84 | return; 85 | } 86 | $versionString = \substr($versionString, 1); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/VersionNumber.php: -------------------------------------------------------------------------------- 1 | , Sebastian Heuer , Sebastian Bergmann 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PharIo\Version; 11 | 12 | class VersionNumber { 13 | 14 | /** @var ?int */ 15 | private $value; 16 | 17 | public function __construct(?int $value) { 18 | $this->value = $value; 19 | } 20 | 21 | public function isAny(): bool { 22 | return $this->value === null; 23 | } 24 | 25 | public function getValue(): ?int { 26 | return $this->value; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/constraints/AbstractVersionConstraint.php: -------------------------------------------------------------------------------- 1 | , Sebastian Heuer , Sebastian Bergmann 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PharIo\Version; 11 | 12 | abstract class AbstractVersionConstraint implements VersionConstraint { 13 | /** @var string */ 14 | private $originalValue; 15 | 16 | public function __construct(string $originalValue) { 17 | $this->originalValue = $originalValue; 18 | } 19 | 20 | public function asString(): string { 21 | return $this->originalValue; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/constraints/AndVersionConstraintGroup.php: -------------------------------------------------------------------------------- 1 | , Sebastian Heuer , Sebastian Bergmann 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PharIo\Version; 11 | 12 | class AndVersionConstraintGroup extends AbstractVersionConstraint { 13 | /** @var VersionConstraint[] */ 14 | private $constraints = []; 15 | 16 | /** 17 | * @param VersionConstraint[] $constraints 18 | */ 19 | public function __construct(string $originalValue, array $constraints) { 20 | parent::__construct($originalValue); 21 | 22 | $this->constraints = $constraints; 23 | } 24 | 25 | public function complies(Version $version): bool { 26 | foreach ($this->constraints as $constraint) { 27 | if (!$constraint->complies($version)) { 28 | return false; 29 | } 30 | } 31 | 32 | return true; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/constraints/AnyVersionConstraint.php: -------------------------------------------------------------------------------- 1 | , Sebastian Heuer , Sebastian Bergmann 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PharIo\Version; 11 | 12 | class AnyVersionConstraint implements VersionConstraint { 13 | public function complies(Version $version): bool { 14 | return true; 15 | } 16 | 17 | public function asString(): string { 18 | return '*'; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/constraints/ExactVersionConstraint.php: -------------------------------------------------------------------------------- 1 | , Sebastian Heuer , Sebastian Bergmann 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PharIo\Version; 11 | 12 | class ExactVersionConstraint extends AbstractVersionConstraint { 13 | public function complies(Version $version): bool { 14 | $other = $version->getVersionString(); 15 | 16 | if ($version->hasBuildMetaData()) { 17 | $other .= '+' . $version->getBuildMetaData()->asString(); 18 | } 19 | 20 | return $this->asString() === $other; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/constraints/GreaterThanOrEqualToVersionConstraint.php: -------------------------------------------------------------------------------- 1 | , Sebastian Heuer , Sebastian Bergmann 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PharIo\Version; 11 | 12 | class GreaterThanOrEqualToVersionConstraint extends AbstractVersionConstraint { 13 | /** @var Version */ 14 | private $minimalVersion; 15 | 16 | public function __construct(string $originalValue, Version $minimalVersion) { 17 | parent::__construct($originalValue); 18 | 19 | $this->minimalVersion = $minimalVersion; 20 | } 21 | 22 | public function complies(Version $version): bool { 23 | return $version->getVersionString() === $this->minimalVersion->getVersionString() 24 | || $version->isGreaterThan($this->minimalVersion); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/constraints/OrVersionConstraintGroup.php: -------------------------------------------------------------------------------- 1 | , Sebastian Heuer , Sebastian Bergmann 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PharIo\Version; 11 | 12 | class OrVersionConstraintGroup extends AbstractVersionConstraint { 13 | /** @var VersionConstraint[] */ 14 | private $constraints = []; 15 | 16 | /** 17 | * @param string $originalValue 18 | * @param VersionConstraint[] $constraints 19 | */ 20 | public function __construct($originalValue, array $constraints) { 21 | parent::__construct($originalValue); 22 | 23 | $this->constraints = $constraints; 24 | } 25 | 26 | public function complies(Version $version): bool { 27 | foreach ($this->constraints as $constraint) { 28 | if ($constraint->complies($version)) { 29 | return true; 30 | } 31 | } 32 | 33 | return false; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/constraints/SpecificMajorAndMinorVersionConstraint.php: -------------------------------------------------------------------------------- 1 | , Sebastian Heuer , Sebastian Bergmann 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PharIo\Version; 11 | 12 | class SpecificMajorAndMinorVersionConstraint extends AbstractVersionConstraint { 13 | /** @var int */ 14 | private $major; 15 | 16 | /** @var int */ 17 | private $minor; 18 | 19 | public function __construct(string $originalValue, int $major, int $minor) { 20 | parent::__construct($originalValue); 21 | 22 | $this->major = $major; 23 | $this->minor = $minor; 24 | } 25 | 26 | public function complies(Version $version): bool { 27 | if ($version->getMajor()->getValue() !== $this->major) { 28 | return false; 29 | } 30 | 31 | return $version->getMinor()->getValue() === $this->minor; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/constraints/SpecificMajorVersionConstraint.php: -------------------------------------------------------------------------------- 1 | , Sebastian Heuer , Sebastian Bergmann 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PharIo\Version; 11 | 12 | class SpecificMajorVersionConstraint extends AbstractVersionConstraint { 13 | /** @var int */ 14 | private $major; 15 | 16 | public function __construct(string $originalValue, int $major) { 17 | parent::__construct($originalValue); 18 | 19 | $this->major = $major; 20 | } 21 | 22 | public function complies(Version $version): bool { 23 | return $version->getMajor()->getValue() === $this->major; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/constraints/VersionConstraint.php: -------------------------------------------------------------------------------- 1 | , Sebastian Heuer , Sebastian Bergmann 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PharIo\Version; 11 | 12 | interface VersionConstraint { 13 | public function complies(Version $version): bool; 14 | 15 | public function asString(): string; 16 | } 17 | -------------------------------------------------------------------------------- /src/exceptions/Exception.php: -------------------------------------------------------------------------------- 1 | , Sebastian Heuer , Sebastian Bergmann 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PharIo\Version; 11 | 12 | use Throwable; 13 | 14 | interface Exception extends Throwable { 15 | } 16 | -------------------------------------------------------------------------------- /src/exceptions/InvalidPreReleaseSuffixException.php: -------------------------------------------------------------------------------- 1 | , Sebastian Heuer , Sebastian Bergmann 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PharIo\Version; 11 | 12 | final class UnsupportedVersionConstraintException extends \RuntimeException implements Exception { 13 | } 14 | --------------------------------------------------------------------------------