skip to content

In PHP, how do you check which version is running, and when should you prefer PHP_VERSION_ID over version_compare()?

level: middleimportance: should knowfreq 36%

answer

  1. string constant vs integer constant
  2. major times 10000 plus minor times 100
  3. version_compare returns -1, 0 or 1
  4. operator argument returns a bool
  5. feature checks beat version checks

basics

~10 s

PHP_VERSION is the version string, such as "8.5.11"; PHP_VERSION_ID is an integer, 80511, that compares with plain < and >=. version_compare() compares arbitrary version strings, including RC and dev suffixes.

solid answer

~40 s

`PHP_VERSION` (and `phpversion()` with no argument) returns the running version as a string, for example `"8.5.11"`. `PHP_VERSION_ID` is an integer built as major × 10000 + minor × 100 + release, so 8.5.11 is `80511`, and a check such as `PHP_VERSION_ID >= 80400` is a fast, unambiguous integer comparison; it is the usual choice for gating code on the running PHP. `version_compare()` is for comparing **arbitrary version strings**, such as a library's version or a string that carries `RC1` or `-dev`: it returns -1, 0 or 1, or a bool when you pass an operator like `'>='`. Where possible, test the capability itself with `function_exists()` or `extension_loaded()`, and enforce the minimum version in Composer rather than at runtime.

code

php · 21 lines
php
<?php
declare(strict_types=1);

$items = ['draft', 'sent'];

// Gate on the running interpreter: integer comparison.
if (PHP_VERSION_ID >= 80500) {
    $first = array_first($items);
} else {
    $first = $items === [] ? null : $items[array_key_first($items)];
}

// Compare arbitrary version strings.
var_dump(version_compare('8.5.0RC1', '8.5.0'));        // int(-1)
var_dump(version_compare('8.5', '8.5.0'));             // int(-1)
var_dump(version_compare(PHP_VERSION, '8.4.0', '>=')); // bool(true) on 8.5.11

// Prefer testing the capability itself.
if (!extension_loaded('intl')) {
    throw new RuntimeException('The intl extension is required.');
}

go deeper

for a junior

Recall that PHP_VERSION is a string and PHP_VERSION_ID an integer, and be able to write a check such as PHP_VERSION_ID >= 80400.

for a middle

Explain how version_compare() canonicalises strings, what it returns with and without an operator, and why fewer parts sort lower.

for a senior

Argue for feature detection with function_exists() or extension_loaded() over version checks, and keep runtime version branches to libraries that must support several branches.

for a principal

Decide where version constraints live across a codebase: in dependency manifests and CI matrices, not in scattered runtime conditionals that nobody removes after the upgrade.

## The constants PHP defines several predefined constants that describe the running interpreter. On PHP 8.5.11 they are: | Constant | Type | Value on 8.5.11 | |---|---|---| | `PHP_VERSION` | string | `"8.5.11"` | | `PHP_MAJOR_VERSION` | int | `8` | | `PHP_MINOR_VERSION` | int | `5` | | `PHP_RELEASE_VERSION` | int | `11` | | `PHP_EXTRA_VERSION` | string | `""` (a suffix such as a distribution tag when present) | | `PHP_VERSION_ID` | int | `80511` | `PHP_VERSION_ID` is computed as **major × 10000 + minor × 100 + release**. The manual's example turns `"5.2.7-extra"` into `50207`: the extra suffix is **not** part of the integer. The function `phpversion()` with no argument returns the same string as `PHP_VERSION`; with an extension name, `phpversion('intl')` returns that extension's version, or `false` if the extension is not loaded or has no version information. ## Checking the running version For the common case, gating code on the interpreter version, `PHP_VERSION_ID` is the cleanest tool: - It is an **integer**, so `PHP_VERSION_ID >= 80400` is an ordinary numeric comparison with no parsing. - It is a **constant**, so there is no function call and static analysers can reason about it. - It avoids string-comparison traps: comparing `PHP_VERSION` with `>=` against `'8.10'` would compare strings, not versions. ## version_compare() `version_compare(string $version1, string $version2, ?string $operator = null): int|bool` compares two "PHP-standardized" version strings: 1. It replaces `_`, `-` and `+` with dots and puts dots around non-numeric parts, so `8.5.0RC1` becomes `8.5.0.RC.1`. 2. It compares the parts left to right: numbers numerically, and special words in the order `dev` < `alpha`/`a` < `beta`/`b` < `RC`/`rc` < a number < `pl`/`p`. 3. Without an operator it returns **-1, 0 or 1**. With one of `<`, `lt`, `<=`, `le`, `>`, `gt`, `>=`, `ge`, `==`, `=`, `eq`, `!=`, `<>`, `ne` it returns **true or false**. 4. An unsupported operator throws a **`ValueError`** on PHP 8.5, because the source rejects it with an argument-value error. Two behaviours catch people out: - **Fewer parts sort lower**: `version_compare('8.5', '8.5.0')` returns `-1`, so `'8.5' == '8.5.0'` under `version_compare` is false. Compare like with like, or use the `>=` operator against the shorter form. - **Pre-releases sort below the release**: `8.5.0RC1` is lower than `8.5.0`, and the special words are case-sensitive, so version strings from outside sources may need lowercasing first. Use `version_compare()` when the input is a string you did not produce yourself: a library version, a value read from a lock file, or a build that carries a suffix. ## Prefer feature detection when you can A version check is only a proxy for "does the thing I need exist here". Often the direct test is clearer and survives backports and custom builds: - `function_exists('array_first')` before relying on a function added in 8.5. - `extension_loaded('intl')` before using an extension that may not be installed. - `class_exists()` or `defined()` for classes and constants. This is the pattern polyfill libraries use: define the function only when it is missing. ## Enforce the minimum outside the code Runtime checks are for code that must run on several versions, typically a library. An application usually targets one version, and the better place to enforce it is the dependency manifest: the `php` requirement in `composer.json` and Composer's generated platform check stop an install or a bootstrap on the wrong interpreter. Those settings belong to Composer's own topic; the point here is that a scatter of `PHP_VERSION_ID` branches inside an application is a smell, while in a library that supports several branches it is normal.

  • Why is comparing PHP_VERSION with the >= operator against a string a bug?
    `PHP_VERSION >= '8.10.0'` compares two strings. Both are numeric-looking, but `8.10.0` is not a valid number, so PHP falls back to a byte-by-byte string comparison, where `'8.5.11'` sorts above `'8.10.0'` because `5` > `1`. Use `PHP_VERSION_ID` for the running version or `version_compare()` for strings.
  • What does phpversion('pdo_mysql') return when the extension is not loaded?
    It returns `false`. With an extension name, `phpversion()` returns that extension's version string, or `false` when the extension is not enabled or has no version information. Test the result with `=== false`, or use `extension_loaded()` when you only need to know whether the extension is present.

saying these in an interview costs you the question

  • PHP_VERSION_ID is a string like "8.5.11".
  • version_compare('8.5', '8.5.0') returns 0 because the missing patch counts as zero.
  • Comparing PHP_VERSION with >= against a string is a safe version check.
  • An application should scatter PHP_VERSION_ID branches instead of declaring a platform requirement.
  • An invalid version_compare() operator just returns null on PHP 8.