rectorphp/rector · error · InvalidEregException

"(" does not have a matching ")"

Error message

"(" does not have a matching ")"

What it means

EregToPcreTransformer::processBracket() parses a '(' group by recursively converting the sub-expression and then requiring the next character to be the closing ')'. If the input ends or the character differs (e.g. a '|' consumed by the recursive branch split), the group is unterminated and InvalidEregException('"(" does not have a matching ")"') is thrown (EregToPcreTransformer.php:194), stopping the ereg-to-preg conversion.

Source

Thrown at rules/Php70/EregToPcreTransformer.php:194

        if ($normalizeUnprintableChar) {
            $content = str_replace("\f", '\\\\f', $content);
        }
        return str_replace($this->pcreDelimiter, '\\' . $this->pcreDelimiter, $content);
    }
    /**
     * @param array<int, mixed> $r
     */
    private function processBracket(string $content, int $i, int $l, array &$r, int $rr): int
    {
        // special case
        if ($i + 1 < $l && $content[$i + 1] === ')') {
            $r[$rr] .= '()';
            ++$i;
        } else {
            $position = $i + 1;
            [$t, $ii] = $this->_ere2pcre($content, $position);
            if ($ii >= $l || $content[$ii] !== ')') {
                throw new InvalidEregException('"(" does not have a matching ")"');
            }
            $r[$rr] .= '(' . $t . ')';
            $i = $ii;
        }
        // retype
        $i = (int) $i;
        return $i;
    }
    /**
     * @return float[]|int[]|string[]
     */
    private function processSquareBracket(string $s, int $i, int $l, string $cls, bool $start): array
    {
        do {
            if ($s[$i] === '[' && $i + 1 < $l && strpos('.=:', $s[$i + 1]) !== \false) {
                /** @var string $cls */
                [$cls, $i] = $this->processCharacterClass($s, $i, $cls);
            } else {

View on GitHub (pinned to 408fcb0ff1)

Solutions

  1. Balance the group in the file Rector reported: '(abc' -> '(abc)'.
  2. While there, check the whole pattern for other unbalanced '('/')' — the fix usually surfaces more.
  3. Re-run rector to confirm conversion; in bulk migrations preflight literal ereg patterns with a bracket-balance check before running the Php70 set.

Example fix

// before - unclosed group
ereg('(abc', $subject);

// after - closed group
ereg('(abc)', $subject); // rector: preg_match('#(abc)#m', $subject)
Defensive patterns

Strategy: try-catch

Validate before calling

function groupsBalanced(string $p): bool {
    $depth = 0;
    for ($i = 0, $n = strlen($p); $i < $n; ++$i) {
        if ($p[$i] === '\\') { ++$i; continue; }
        if ($p[$i] === '(') { ++$depth; }
        elseif ($p[$i] === ')') { --$depth; if ($depth < 0) return false; }
    }
    return $depth === 0;
}

Try / catch

try {
    $pcre = $eregToPcreTransformer->transform($pattern, $ignoreCase);
} catch (InvalidEregException $e) {
    // group opened but never closed — balance '(' and ')' then re-run
    continue;
}

Prevention

When it happens

Trigger: Rector (Php70 set / EregToPregMatchRector, target PHP >= 7.0) processes an ereg-family call with a literal pattern containing an unclosed group: `ereg('(abc', $s)`, `split('(a|b', $s)`, or nested cases like '((a)' where an inner '(' is never closed. The recursive _ere2pcre() returns an index that is not a ')' and the exception fires.

Common situations: Legacy patterns with a missing ')' after hand edits; alternations where the author intended '(a|b)' but wrote '(a|b' ; long patterns where balancing brackets by eye failed; patterns converted from other regex dialects that group differently.

Related errors


AI-assisted analysis of rectorphp/rector@408fcb0ff1 (2026-08-21). Data as JSON: /api/errors/3c716c8a85e4c368. Report an issue: GitHub.