sebastianbergmann/phpunit · error · PHPUnit\Framework\Exception

Regular expression cannot be matched: %s

Error message

Regular expression cannot be matched: %s

What it means

The RegularExpression constraint (matchesRegularExpression() / assertThat with regularExpression()) runs preg_match() with the @ operator; if preg_match returns false, PCRE itself failed and PHPUnit rethrows the PCRE error message. This is not a mismatch (which returns false) — it is the regex engine erroring out, most often a backtrack or recursion limit exceeded by a catastrophic pattern, or invalid UTF-8 in the subject when the pattern uses the /u modifier.

Source

Thrown at src/Framework/Constraint/String/RegularExpression.php:74

        );
    }

    /**
     * Evaluates the constraint for parameter $other. Returns true if the
     * constraint is met, false otherwise.
     *
     * @throws FrameworkException
     */
    protected function matches(mixed $other): bool
    {
        if (!is_string($other)) {
            return false;
        }

        $matches = @preg_match($this->pattern, $other);

        if ($matches === false) {
            throw new FrameworkException(
                sprintf(
                    'Regular expression cannot be matched: %s',
                    preg_last_error_msg(),
                ),
            );
        }

        return $matches > 0;
    }
}

View on GitHub (pinned to f123cdb2a2)

Solutions

  1. Simplify the pattern: replace nested/lazy catch-alls with anchored, specific subpatterns or use preg_match on smaller slices of the subject
  2. Raise the PCRE limits for the test run: ini_set('pcre.backtrack_limit', '10000000'); (and pcre.recursion_limit if the error names recursion)
  3. If the error names UTF-8, fix the subject encoding (mb_convert_encoding to UTF-8) or drop the /u modifier when no unicode properties are needed
  4. Sanitize data-driven patterns: validate them once with @preg_match($pattern, '') === false && preg_last_error() check before use

Example fix

// before
$this->matchesRegularExpression('/^(.*\n)+TOTAL: \\d+$/', $longReport); // backtrack limit

// after
$this->matchesRegularExpression('/^.*\nTOTAL: \\d+$/s', $longReport);
// or in the test bootstrap:
ini_set('pcre.backtrack_limit', '10000000');
Defensive patterns

Strategy: validation

Validate before calling

// Validate the pattern and raise limits before the assertion:
if (@preg_match($pattern, '') === false) {
    $this->markTestSkipped('Invalid regex: ' . preg_last_error_msg());
}
ini_set('pcre.backtrack_limit', '10000000');

$this->matchesRegularExpression($pattern, $subject);

Type guard

function isMatchablePattern(string $pattern): bool
{
    return @preg_match($pattern, '') !== false;
}

Try / catch

use PHPUnit\Framework\Exception as FrameworkException;

try {
    $this->matchesRegularExpression($pattern, $subject);
} catch (FrameworkException $e) {
    if (str_contains($e->getMessage(), 'Regular expression cannot be matched')) {
        ini_set('pcre.backtrack_limit', '10000000'); // then re-run once
    }
}

Prevention

When it happens

Trigger: matchesRegularExpression($pattern, $subject) where the pattern causes catastrophic backtracking (nested quantifiers like (a+)+) on long subjects exceeding pcre.backtrack_limit, deep recursion exceeding pcre.recursion_limit, or where $subject contains invalid UTF-8 while the pattern carries the u modifier.

Common situations: Golden-output tests matching long log/console strings with loose patterns like '.*foo.*bar.*'; patterns authored with (.*)* ; subjects read from binary-ish files or external APIs with broken encoding; CI boxes where pcre.backtrack_limit is lower than a developer machine; PCRE vs PCRE2 differences across PHP versions.

Related errors


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