Named Capture Groups
debt(d7/e3/b3/t5)
Closest to 'only careful code review or runtime testing' (d7), since phpstan/phpcs don't flag numbered group usage as a smell — it requires code review to spot brittle $matches[1] index access.
Closest to 'simple parameterised fix' (e3), per quick_fix: rename pattern groups to (?P<name>...) and update $matches[N] to $matches['name'] — small refactor within the regex usage site, possibly a few lines per call site.
Closest to 'localised tax' (b3), since the choice applies per-regex; impact is on the file using that pattern, not the whole codebase, though regex usage is spread across web/cli/queue contexts.
Closest to 'notable trap' (t5), per misconception: developers underestimate that numbered indices silently shift when groups are added/reordered — a documented gotcha that bites once experienced.
Also Known As
TL;DR
Explanation
Named capture groups in PHP/PCRE: (?P<n>pattern) or (?<n>pattern). Access via $matches['name'] instead of $matches[1]. Benefits: self-documenting patterns, index-independent (reordering groups does not break code), easier to maintain. Backreferences: (?P=name) in pattern, ${name} in preg_replace replacement strings.
Common Misconception
Why It Matters
Common Mistakes
- Numbered groups when there are more than 3
- Not using ${name} in preg_replace replacement strings
- Naming groups in alternations where only one branch matches — others return empty string
- Group names with invalid characters — must match [A-Za-z_][A-Za-z0-9_]*
Code Examples
// Numbered groups — fragile:
preg_match('/(\d{4})-(\d{2})-(\d{2})/', '2026-03-16', $m);
$year = $m[1]; // Breaks if pattern is reordered
$month = $m[2];
$day = $m[3];
// Named groups — readable and stable:
preg_match('/(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})/', '2026-03-16', $m);
$year = $m['year']; // Self-documenting
$month = $m['month'];
$day = $m['day'];
// Named backreference:
preg_match('/<(?P<tag>\w+)>[^<]*<\/(?P=tag)>/', '<b>bold</b>', $m);
// (?P=tag) ensures opening and closing tag are the same