sebastianbergmann/phpunit · error · PHPUnit\TextUI\XmlConfiguration\Exception

Invalid version comparison operator: "%s"

Error message

Invalid version comparison operator: "%s"

What it means

The phpVersionOperator attribute on <directory> and <file> entries inside <testsuite> (and source restrictions) must be one of PHP's version_compare() operators: !=, <, <=, <>, =, ==, >, >=, eq, ge, gt, le, lt, ne. parseVersionOperator() throws for anything else. This is version_compare syntax, not composer semver syntax, so '^', '~', '=~' and similar are invalid.

Source

Thrown at src/TextUI/Configuration/Xml/Loader.php:976

        $value = $element->getAttribute($attribute);

        if ($value === '') {
            return $default;
        }

        return $value;
    }

    /**
     * @throws Exception
     *
     * @return '!='|'<'|'<='|'<>'|'='|'=='|'>'|'>='|'eq'|'ge'|'gt'|'le'|'lt'|'ne'
     */
    private function parseVersionOperator(string $operator): string
    {
        return match ($operator) {
            '!=', '<', '<=', '<>', '=', '==', '>', '>=', 'eq', 'ge', 'gt', 'le', 'lt', 'ne' => $operator,
            default                                                                         => throw new Exception(sprintf('Invalid version comparison operator: "%s"', $operator)),
        };
    }

    private function parseInteger(string $value, int $default): int
    {
        if (is_numeric($value)) {
            return (int) $value;
        }

        return $default;
    }

    private function php(string $filename, DOMXPath $xpath): Php
    {
        $includePaths = [];

        $includePathNodes = $xpath->query('php/includePath');

View on GitHub (pinned to f123cdb2a2)

Solutions

  1. Use a version_compare operator: `<`, `<=`, `>`, `>=`, `=`, `==`, `!=`, `<>` or the word forms lt, le, gt, ge, eq, ne
  2. For 'PHP 8.1 or newer' use phpVersion="8.1.0" phpVersionOperator=">="
  3. Remember the comparison uses PHP's version_compare() rules, so write full version strings like 8.1.0

Example fix

<!-- before -->
<directory phpVersion="8.1" phpVersionOperator="^">tests/Php81</directory>

<!-- after -->
<directory phpVersion="8.1.0" phpVersionOperator=">=">tests/Php81</directory>
Defensive patterns

Strategy: validation

Validate before calling

const VALID_OPERATORS = ['!=', '<', '<=', '<>', '=', '==', '>', '>=', 'eq', 'ge', 'gt', 'le', 'lt', 'ne'];

function validPhpVersionOperator(string $operator): bool
{
    return in_array($operator, VALID_OPERATORS, true);
}

// before writing/generating config:
if (!validPhpVersionOperator($cfg['operator'])) {
    throw new InvalidArgumentException('phpVersionOperator must be a version_compare() operator');
}

Type guard

/**
 * @param string $operator
 * @return '!='|'<'|'<='|'<>'|'='|'=='|'>'|'>='|'eq'|'ge'|'gt'|'le'|'lt'|'ne'
 */
function narrowVersionOperator(string $operator): string
{
    return in_array($operator, ['!=', '<', '<=', '<>', '=', '==', '>', '>=', 'eq', 'ge', 'gt', 'le', 'lt', 'ne'], true)
        ? $operator
        : throw new InvalidArgumentException("Invalid version comparison operator: {$operator}");
}

Try / catch

try {
    $configuration = (new \PHPUnit\TextUI\XmlConfiguration\Loader)->loadFile('phpunit.xml');
} catch (\PHPUnit\TextUI\XmlConfiguration\Exception $e) {
    if (str_contains($e->getMessage(), 'Invalid version comparison operator')) {
        // fix the phpVersionOperator attribute to a version_compare() operator
    }
}

Prevention

When it happens

Trigger: `<directory phpVersion="8.1" phpVersionOperator="^">tests</directory>`; phpVersionOperator="=~"; phpVersionOperator="==="; misspellings like "greate"; copying a composer.json constraint into the attribute.

Common situations: Developers familiar with composer version constraints writing phpunit.xml by hand; copy-pasted snippets from blogs using semver operators; config generators emitting wrong operator strings.

Related errors


AI-assisted analysis of sebastianbergmann/phpunit@f123cdb2a2 (2026-08-23). Data as JSON: /api/errors/ef1948d680b523d7. Report an issue: GitHub.