composer/semver
A standalone PHP library for parsing, validating, and comparing semantic version numbers and constraints, extracted from Composer. It normalizes versions, handles stability flags and branches, and powers dependency version checks. Requires PHP 5.3.2+.
Install with composer require composer/semver — the ^3.5 constraint already allows 3.5.1, but if your lock file resolves to 3.5.0 run composer update composer/semver to pick up the 3.5.1 Intervals::isSubsetOf() fix (see Gotchas). The core entry points are VersionParser, Comparator, Semver, Intervals, and CompilingMatcher — all in the Composer\Semver namespace.
The most common first use case is validating and normalizing version strings (e.g. cleaning "1.2" → "1.2.0.0") or parsing constraints like "^1.2 || ~2.0" for downstream matching.
use Composer\Semver\VersionParser;
use Composer\Semver\Semver;
$parser = new VersionParser();
$parser->isValid('1.2'); // true (added in 3.5.0)
$normalized = $parser->normalize('1.2'); // '1.2.0.0'
$constraint = $parser->parseConstraints('^1.2 || ~2.0');
Semver::satisfies($normalized, '^1.2 || ~2.0'); // true
Check validity first with VersionParser::isValid() (3.5.0+) — on older versions normalize() throws UnexpectedValueException, so you'd need a try/catch instead. Use Semver::satisfies() for constraint checks and Semver::sort()/rsort() to order arrays of version strings intelligently.
VersionParser::parseConstraints() to convert user- or config-provided strings (e.g. from composer.json or CLI flags) into ConstraintInterface objects. Guard the input with isValid() (3.5.0+) so malformed strings fail fast with a clean validation error rather than an exception from normalize(). Then optimize hot constraints with Intervals::compactConstraint() — but only on 3.5.0+, see Gotchas.Semver::satisfies() and Semver::satisfiedBy() with normalized versions. Use Comparator for low-level binary comparisons (e.g. sorting UI displays).>=1.0-RC1, <2.0-beta2, etc. is now consistent across alpha, beta, and rc suffixes in 3.5.0. If you gate features on RC versions, write tests that assert both the boundary version and its immediate neighbours, and require ^3.5 in composer.json.Intervals::get() to decompose constraints into numeric intervals. Combine with Intervals::isSubsetOf() to check coverage (e.g. "does constraint A fully cover B?") — useful for security scanning or update feasibility checks. Note that a constraint matching nothing (e.g. < 0.0.0) is trivially a subset of any constraint; that result is only correct on 3.5.1+, so require ^3.5.1 when your coverage logic can encounter empty ranges.CompilingMatcher and call CompilingMatcher::clear() after each batch. 3.5.0 removes per-call cache-key string concatenation, so large scans are measurably cheaper — worth re-benchmarking if you previously avoided it."dev-main" using VersionParser::normalizeBranch(). It handles aliases (e.g. foobar-dev → dev-foobar) and preserves -dev stability markers for downstream logic.isSubsetOf() correctness on empty ranges: On 3.5.0, Intervals::isSubsetOf() returns false for < 0.0.0, even though that constraint matches nothing and is therefore a subset of every constraint. If you use subset checks to answer "is every version in A also in B?" — typical in patch-coverage or security-scan logic — a 3.5.0 false negative can flip your conclusion. Bump to 3.5.1 (composer update composer/semver) and add a regression test asserting isSubsetOf() behaviour for an unsatisfiable range.compactConstraint() correctness: Prior to 3.5.0, Intervals::compactConstraint() could turn a constraint that matches no dev versions (e.g. > 1.0.0 != 2.0.0 || < 1.9.0) into != 2.0.0, which does match all dev-* versions. If you use compaction in dependency-resolution or security-scan logic, this silently widens your match set. Require ^3.5 or skip compaction on older versions.>=/< constraints on RC versions (e.g. >=1.0-RC1) picked up an extra -dev suffix that alpha/beta constraints did not, producing inconsistent boundary behaviour. Pin ^3.5 if you compare against RC releases.isValid() availability: VersionParser::isValid() is new in 3.5.0. On older versions, wrap normalize() in a try/catch for UnexpectedValueException, or check with Semver::satisfies() defensively.normalizeStability() expects valid stabilities (dev, alpha, beta, rc, stable). Validate user input with parseStability() first; treat unparseable values as invalid rather than assuming they fall back to stable.^ constraints, * no longer matches dev-* versions (since v3.0.0); * matches stable only, so pre-release installs must be explicit (*@dev or an explicit dev-* constraint)."1.0" and "1.0.0.0" behave differently under version_compare; always normalize via VersionParser::normalize() before using Comparator or Semver.CompilingMatcher Lifecycle: It memoizes aggressively by design. Call CompilingMatcher::clear() between batches/requests in long-running workers (Octane, queue workers) to avoid stale results and unbounded memory growth.ConstraintInterface yourself — it's marked internal as of v3.0. Use MultiConstraint::create() to generate optimized multi-constraints from arrays of strings instead.~ constraints support date-style versions (e.g. ~201903.0), but they're fuzzy — always test edge cases if relying on them, and avoid mixing with standard semver unless intentional.How can I help you explore Laravel packages today?