major = $major; $this->minor = $minor; $this->patch = $patch; $this->prerelease = empty($prerelease) ? [] : explode('.', $prerelease); $this->build = empty($build) ? [] : explode('.', $build); } /** * Gets the value of the major version component. * * @return int Major version component. */ public function getMajor(): int { return $this->major; } /** * Gets the value of the minor version component. * * @return int Minor version component. */ public function getMinor(): int { return $this->minor; } /** * Gets the value of the patch version component. * * @return int Patch version component. */ public function getPatch(): int { return $this->patch; } /** * Gets the split value of the prerelease component. * * @return array array containing the prerelease parts. */ public function getPrerelease(): array { return $this->prerelease; } /** * Gets the split value of the build component. * * @return array array contains the build parts. */ public function getBuild(): array { return $this->build; } /** * Tests if this Version instance represents a prerelease according to the SemVer spec. * * @return bool true if the version indicates a prerelease, false if not. */ public function isPrerelease(): bool { return $this->major < 1 || !empty($this->prerelease); } /** * Creates a new instance of Version with the major component incremented. * * This will implicitly drop prerelease and build info and reset the minor and patch component. * * @return Version the newly created instance of Version. */ public function incrementMajor(): Version { return new Version($this->major + 1, 0, 0); } /** * Creates a new instance of Version with the minor component incremented. * * This will implicitly drop the prerelease and build info and reset the patch component. * * @return Version the newly created instance of Version. */ public function incrementMinor(): Version { return new Version($this->major, $this->minor + 1, 0); } /** * Creates a new instance of Version with the patch component incremented. * * This will implicitly drop the prerelease and build info. * * @return Version the newly created instance of Version. */ public function incrementPatch(): Version { return new Version($this->major, $this->minor, $this->patch + 1); } /** * Checks this instance of Version with another for equality. * * The build component is ignored, as per the SemVer spec. * * @param mixed $other Another instance of Version. * @return bool true if the instances are equal, false if not. */ public function equals(mixed $other): bool { return $other instanceof Version && $other->major === $this->major && $other->minor === $this->minor && $other->patch === $this->patch && XArray::sequenceEquals($this->prerelease, $other->prerelease); } /** * Compares this instance of Version with another. * * The build component is ignored and prerelease component is compared as per the SemVer spec. * * @param mixed $other Another instance of Version. */ public function compare(mixed $other): int { if(!($other instanceof Version)) return PHP_INT_MIN; $diff = $this->major <=> $other->major; if($diff) return $diff; $diff = $this->minor <=> $other->minor; if($diff) return $diff; $diff = $this->patch <=> $other->patch; if($diff) return $diff; $tpi = XArray::extractIterator($this->prerelease); $opi = XArray::extractIterator($other->prerelease); $tpi->rewind(); $opi->rewind(); $valid = $tpi->valid(); $diff = $opi->valid() <=> $valid; if($diff) return $diff; while($valid) { $diff = $opi->current()->compare($tpi->current()); if($diff) return $diff; $tpi->next(); $opi->next(); $valid = $tpi->valid(); $diff = $opi->valid() <=> $valid; if($diff) return $diff; } return 0; } public function __toString(): string { if($this->versionString === null) { $string = $this->major . '.' . $this->minor . '.' . $this->patch; if(!empty($this->prerelease)) $string .= '-' . implode('.', $this->prerelease); if(!empty($this->build)) $string .= '+' . implode('.', $this->build); $this->versionString = $string; } return $this->versionString; } /** * Returns an empty Version instance. * * @return Version Instance of Version with all values set to 0. */ public static function empty(): Version { if(self::$empty === null) self::$empty = new Version(0); return self::$empty; } /** * Parses a version string. * * Parses a version string with a regex adapted from SemVer's documentation. Modified to allow an optional v prefix. * * @see https://semver.org/spec/v2.0.0.html#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string * @param string $versionString String containing a version number. * @throws InvalidArgumentException Provided string does not represent a valid version. * @return Version An instance of Version representing the provided version string. */ public static function parse(string $versionString): Version { // Regex adapted from https://semver.org/spec/v2.0.0.html#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string if(!preg_match('#^v?(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$#', $versionString, $matches)) throw new InvalidArgumentException('$versionString is not a valid version string.'); return new Version( (int)$matches['major'], (int)$matches['minor'], (int)$matches['patch'], $matches['prerelease'] ?? '', $matches['build'] ?? '' ); } }