sebastianbergmann/phpunit · error · FrameworkException
Format description cannot be matched: %s
Error message
Format description cannot be matched: %s
What it means
assertStringMatchesFormat() translates its format description (%s, %d, %a, %A, %e, %f, %x, %c, %r...) into a single anchored PCRE regex ('/^...$/s') and runs preg_match(). If PCRE fails on that generated regex (returns false), PHPUnit throws this exception carrying preg_last_error_msg(). The culprit is almost always the generated pattern over-running pcre.backtrack_limit/pcre.recursion_limit on the actual string, not the format syntax itself.
Source
Thrown at src/Framework/Constraint/String/StringMatchesFormatDescription.php:86
* @throws FrameworkException
*/
protected function matches(mixed $other): bool
{
if (!is_string($other)) {
return false;
}
$other = $this->convertNewlines($other);
$matches = @preg_match(
$this->regularExpressionForFormatDescription(
$this->convertNewlines($this->formatDescription),
),
$other,
);
if ($matches === false) {
throw new FrameworkException(
sprintf(
'Format description cannot be matched: %s',
preg_last_error_msg(),
),
);
}
return $matches > 0;
}
protected function failureDescription(mixed $other): string
{
return 'string matches format description';
}
protected function failureDescriptionInContext(Operator $operator, mixed $role, mixed $other): string
{
// @codeCoverageIgnoreStartView on GitHub (pinned to f123cdb2a2)
Solutions
- Reduce placeholder density: anchor with more literal lines between %a/%A placeholders so the regex has less ambiguity
- Split the assertion: assert the stable parts with assertStringContainsString() and only variable tokens with assertStringMatchesFormat()
- Raise the limit in phpunit.xml or bootstrap: <ini name="pcre.backtrack_limit" value="10000000"/>
- For huge outputs, prefer assertStringEqualsStringIgnoringLineEndings or a normalized diff comparison instead of one giant format regex
Example fix
// before
$this->assertStringMatchesFormat(
"Header: %s\n%A\nBody: %a\n%A\nFooter: %s",
$hugeOutput // PCRE backtrack limit exhausted
);
// after
$this->assertStringContainsString('Header: ', $hugeOutput);
$this->assertStringContainsString('Footer: ', $hugeOutput);
// or raise the limit:
// <ini name="pcre.backtrack_limit" value="10000000"/> in phpunit.xml Defensive patterns
Strategy: validation
Validate before calling
// Keep formats small: assert stable literals with contains, placeholders only where needed:
foreach (['Header: ', 'Footer: '] as $literal) {
if (!str_contains($actual, $literal)) {
$this->fail("Missing literal: $literal");
}
}
$this->assertStringMatchesFormat("Body: %d lines", $bodyLine); Try / catch
use PHPUnit\Framework\Exception as FrameworkException;
try {
$this->assertStringMatchesFormat($format, $actual);
} catch (FrameworkException $e) {
if (str_contains($e->getMessage(), 'Format description cannot be matched')) {
$this->markTestIncomplete('Format regex hit PCRE limit: ' . $e->getMessage());
}
} Prevention
- Anchor %a/%A placeholders with literal lines instead of stacking wildcards
- Raise <ini name="pcre.backtrack_limit"/> in phpunit.xml for output-heavy suites
- For very large documents prefer string diff assertions over one giant format regex
When it happens
Trigger: assertStringMatchesFormat($format, $actual) where the format contains multiple greedy or lazy multiline placeholders (%a, %A, %s chains) applied to a long multi-line string, driving backtracking past pcre.backtrack_limit; or the actual string is invalid UTF-8 relative to an implied unicode context.
Common situations: Snapshot-ish tests asserting CLI/artisan command output, API responses or templated emails with '%a ... %a ... %a' between concrete lines; large payloads in CI where the PHP pcre.backtrack_limit default (1M) is exceeded; formats copied from sprintf() templates with dozens of placeholders.
Related errors
- Regular expression cannot be matched: %s
- Subscriber "%s" does not implement any known interface - did
- Unknown event type "%s"
- Subscriber "%s" does not implement a known interface
- Subscriber "%s" does not exist or is not an interface
AI-assisted analysis of sebastianbergmann/phpunit@f123cdb2a2 (2026-08-23).
Data as JSON: /api/errors/49bc27957c6bec5e.
Report an issue: GitHub.