Diagnostics Cheat Sheet
Fast fixes for the most common PHPRegex diagnostics. Use this as a quick reference when you encounter an issue.
Quick Fix Index
| Diagnostic | Quick Fix |
|---|---|
| Lookbehind is unbounded | Add bounds or use \K |
| Backreference to non-existent group | Check group numbers/names |
| Duplicate group name | Use unique names |
| Invalid quantifier range | Swap min/max |
| Nested quantifiers detected | Use atomic groups |
| Dot-star in repetition | Use atomic/possessive |
| Overlapping alternation branches | Atomic or simplify |
| Duplicate alternation branch | Remove duplicate |
| Empty alternative | Use ? quantifier |
| Redundant non-capturing group | Remove group |
| Suspicious ASCII range | Split A-Z and a-z |
| Alternation-like character class | Use (foo|bar) |
| Useless backreference | Move or remove |
| Concatenated quantifiers | Tighten or drop quantifier |
| Useless flag | Remove flag |
| Invalid delimiter | Use proper delimiter |
| Delimiter inside a class or comment | Escape it or change delimiter |
| Pattern Too Long | Raise the limit or shorten |
Lookbehind is unbounded
Problem: PCRE requires lookbehinds to have a bounded maximum length.
// ERROR
preg_match('/(?<=a+)b/', $input);
// FIX 1: Use a fixed length
preg_match('/(?<=a{100})b/', $input);
// FIX 2: Use a bounded quantifier (variable length needs PCRE2 10.43+, PHP 8.4)
preg_match('/(?<=a{1,100})b/', $input);
// FIX 3: Restart the match after the run
preg_match('/a+\Kb/', $input); // matches "b" on "aaab"
Backreference to non-existent group
Problem: The backreference points to a group that doesn’t exist.
// ERROR: \2 doesn't exist (only one group)
preg_match('/(\w+)\2/', $input);
// FIX 1: Use correct group number
preg_match('/(\w+)\1/', $input);
// FIX 2: Add the missing group
preg_match('/(\w+)(\w+)\2/', $input); // Now \2 exists
// For named backreferences
// ERROR: No group named 'name'
preg_match('/(?<name>\w+)\k<other>/', $input);
// FIX: Use correct name
preg_match('/(?<name>\w+)\k<name>/', $input);
Duplicate group name
Problem: Named groups must have unique names (unless (?J) is set).
// ERROR: 'id' appears twice
preg_match('/(?<id>\w+)(?<id>\d+)/', $input);
// FIX 1: Use unique names
preg_match('/(?<id>\w+)(?<number>\d+)/', $input);
// FIX 2: Enable J flag for duplicates
preg_match('/(?J)(?<id>\w+)(?<id>\d+)/', $input);
Invalid quantifier range
Problem: Quantifier minimum exceeds maximum.
// ERROR: {5,2} is invalid
preg_match('/\d{5,2}/', $input);
// FIX: Swap to {2,5}
preg_match('/\d{2,5}/', $input);
Nested quantifiers detected
Problem: Nested variable quantifiers can cause ReDoS.
// WARNING: (a+)+ can explode
preg_match('/(a+)+b/', $input);
// FIX 1: Use atomic group
preg_match('/(?>a+)+b/', $input);
// FIX 2: Use possessive quantifier
preg_match('/(a++)+b/', $input);
// FIX 3: Simplify (often equivalent)
preg_match('/a+b/', $input);
Dot-star in repetition
Problem: .* inside + or * can cause extreme backtracking.
// RISKY: .* in + repetition
preg_match('/(?:.*)+/', $input);
// FIX 1: A possessive dot-star reads the run once, no outer repetition
preg_match('/.*+/', $input);
// FIX 2: Use a specific character class
preg_match('/[^x]*x/', $input); // Reads up to the next 'x' at once
Overlapping alternation branches
Problem: One branch is a prefix of another inside repetition.
// WARNING: 'a' and 'aa' overlap
preg_match('/(a|aa)+b/', $input);
// FIX 1: Use atomic group
preg_match('/(?>a|aa)+b/', $input);
// FIX 2: Simplify
preg_match('/a+b/', $input);
Duplicate alternation branch
Problem: The same alternative appears more than once.
// WARNING: Duplicate branch
preg_match('/(foo|foo)/', $input);
// FIX: Keep one copy
preg_match('/foo/', $input);
Empty alternative
Problem: An alternation includes an empty branch (e.g., trailing |).
// WARNING: Empty alternative
preg_match('/foo|/', $input);
// FIX: Use a quantifier instead — the whole branch becomes optional
preg_match('/(?:foo)?/', $input);
Redundant non-capturing group
Problem: Group wraps single token without changing behavior.
// WARNING
preg_match('/(?:foo)/', $input);
// FIX: Remove group
preg_match('/foo/', $input);
When groups ARE needed:
// Needed: Change precedence
preg_match('/(?:foo|bar)baz/', $input); // Groups foo|bar
// Needed: Apply quantifier to multiple
preg_match('/(?:foo)+/', $input); // Repeats "foo"
Suspicious ASCII range
Problem: [A-z] spans ASCII punctuation between Z and a.
// WARNING
preg_match('/[A-z]/', $input);
// FIX
preg_match('/[A-Za-z]/', $input);
Alternation-like character class
Problem: | is literal inside [], so [error|failure] matches single characters.
// WARNING
preg_match('/[error|failure]/', $input);
// FIX
preg_match('/(error|failure)/', $input);
Useless backreference
Problem: The backreference is used before its group is set or the group is always empty.
// WARNING: Backreference before group closes
preg_match('/\1(a)/', $input);
// FIX: Move the backreference
preg_match('/(a)\1/', $input);
Concatenated quantifiers
Problem: Adjacent quantifiers can be simplified when one set is a subset of the other.
// WARNING: \d is a subset of \w
preg_match('/\d+\w+/', $input);
// FIX: Tighten the smaller quantifier
preg_match('/\d\w+/', $input);
// WARNING: \d* before \w* can be dropped — \w covers it
preg_match('/\d*\w*/', $input);
// FIX: Drop the whole quantified term
preg_match('/\w*/', $input);
Useless flag
Problem: Flag has no effect on the pattern.
// WARNING: 's' flag (DotAll) does nothing
preg_match('/^\d+$/s', $input);
// FIX: Remove unused flag
preg_match('/^\d+$/', $input);
// WARNING: 'm' flag does nothing
preg_match('/foo/m', $input);
// FIX: Remove or add anchors
preg_match('/foo/', $input); // or
preg_match('/^foo$/m', $input); // with anchors
// WARNING: 'i' flag does nothing
preg_match('/^\d{4}-\d{2}-\d{2}$/i', $input);
// FIX: Remove
preg_match('/^\d{4}-\d{2}-\d{2}$/', $input);
// WARNING: 'D' flag does nothing (no $, or every $ under m)
preg_match('/^\d+\z/D', $input);
// WARNING: 'x' flag does nothing (no whitespace, no # comment)
preg_match('/^\d+$/x', $input);
Invalid delimiter
Problem: The pattern opens with a character PHP refuses as a delimiter: alphanumeric, a backslash or a NUL byte.
// ERROR: "a" is alphanumeric and cannot delimit
preg_match('a{2}b{2}a', $input);
// PHP warning: Delimiter must not be alphanumeric, backslash, or NUL byte
// FIX 1: Use a non-alphanumeric delimiter
preg_match('/a{2}b{2}a/', $input);
// FIX 2: Use a delimiter the pattern does not contain
preg_match('#a{2}b{2}a#', $input);
A space inside the pattern is not a delimiter problem: /^pattern $/ is valid
and matches "pattern " — the space is a literal character of the pattern.
Delimiter inside a class or comment
Problem: PHP ends the pattern at the first delimiter that is not escaped, even inside [...], (?#...) or \Q...\E.
What follows becomes modifiers, so the call fails with a warning and returns null or false.
// ERROR: the "/" in "://" and in the class ends the pattern ("Unknown modifier")
preg_replace('/([[:alnum:]]+):\/\/([[:alnum:]#?/&=]+)/i', '<$0>', $text);
// FIX 1: Escape every delimiter, classes included
preg_replace('/([[:alnum:]]+):\/\/([[:alnum:]#?\/&=]+)/i', '<$0>', $text);
// FIX 2: Use a delimiter the pattern does not contain
preg_replace('#([[:alnum:]]+)://([[:alnum:]\#?/&=]+)#i', '<$0>', $text);
Pattern Too Long
Problem: Pattern exceeds configured maximum length.
use PHPRegex\Toolkit\Regex;
// ERROR: Pattern too long
preg_match('/very long pattern.../', $input);
// FIX 1: Increase limit (if appropriate)
$regex = Regex::create(['max_pattern_length' => 500000]);
// FIX 2: Shorten the pattern
// Consider splitting or simplifying
Where to Look Next
| Topic | Resource |
|---|---|
| Rule reference | Lint rule reference |
| Diagnostics deep dive | Diagnostics and error messages |
| ReDoS patterns | ReDoS guide |
| API reference | API reference |