:ver" verglich lexikografisch. * Damit galt '1.9.0' als neuer als '1.10.0' und Clients bekamen ein Downgrade * als Update angeboten. Der .NET-Client verglich bereits korrekt - Server und * Client waren sich also uneinig. * * Unterstuetzt: "1.2.3", "v1.2.3", "1.2.3-beta.1", "1.2.3+build.5", "1.2". */ final class Version { /** * @return int -1 wenn $a < $b, 0 bei Gleichstand, 1 wenn $a > $b */ public static function compare(string $a, string $b): int { [$coreA, $preA] = self::parse($a); [$coreB, $preB] = self::parse($b); $length = max(count($coreA), count($coreB)); for ($i = 0; $i < $length; $i++) { $partA = $coreA[$i] ?? 0; $partB = $coreB[$i] ?? 0; if ($partA !== $partB) { return $partA <=> $partB; } } // Eine Version ohne Vorabkennung ist hoeher als dieselbe mit // (1.0.0 > 1.0.0-rc.1), so verlangt es die Semver-Spezifikation. if ($preA === [] && $preB === []) { return 0; } if ($preA === []) { return 1; } if ($preB === []) { return -1; } return self::comparePrerelease($preA, $preB); } public static function isNewer(string $candidate, string $current): bool { return self::compare($candidate, $current) > 0; } /** * Waehlt die hoechste Version aus einer Liste von Release-Datensaetzen. * * @param list> $releases * @return array|null */ public static function highest(array $releases, string $versionKey = 'version'): ?array { $best = null; foreach ($releases as $release) { if (!isset($release[$versionKey]) || !is_string($release[$versionKey])) { continue; } if ($best === null || self::compare($release[$versionKey], (string)$best[$versionKey]) > 0) { $best = $release; } } return $best; } /** * Zerlegt eine Version in numerischen Kern und Vorabkennung. * * @return array{0:list,1:list} */ private static function parse(string $version): array { $version = trim($version); $version = ltrim($version, 'vV'); // Build-Metadaten sind fuer die Rangfolge irrelevant. $plus = strpos($version, '+'); if ($plus !== false) { $version = substr($version, 0, $plus); } $prerelease = []; $dash = strpos($version, '-'); if ($dash !== false) { $preString = substr($version, $dash + 1); $version = substr($version, 0, $dash); $prerelease = $preString === '' ? [] : explode('.', $preString); } $core = []; foreach (explode('.', $version) as $part) { $core[] = (int)preg_replace('/\D/', '', $part); } if ($core === []) { $core = [0]; } return [$core, $prerelease]; } /** * @param list $a * @param list $b */ private static function comparePrerelease(array $a, array $b): int { $length = max(count($a), count($b)); for ($i = 0; $i < $length; $i++) { // Weniger Bestandteile = niedrigere Rangfolge (rc < rc.1) if (!isset($a[$i])) { return -1; } if (!isset($b[$i])) { return 1; } $partA = $a[$i]; $partB = $b[$i]; $numericA = ctype_digit($partA); $numericB = ctype_digit($partB); if ($numericA && $numericB) { $cmp = (int)$partA <=> (int)$partB; if ($cmp !== 0) { return $cmp; } continue; } // Rein numerische Bestandteile rangieren unter alphanumerischen. if ($numericA !== $numericB) { return $numericA ? -1 : 1; } $cmp = strcmp($partA, $partB); if ($cmp !== 0) { return $cmp > 0 ? 1 : -1; } } return 0; } }