Search code examples
phpregexpreg-replace-callback

Using different names for subpatterns of the same number with preg_replace_callback


I'm having a hard time getting my head around what exactly is being numbered in my regex subpatterns. I'm being given the PHP warning:

PHP Warning: preg_replace_callback(): Compilation failed: different names for subpatterns of the same number are not allowed

When attempting the following:

$input = "A string that contains [link-ssec-34] and a [i]word[/i] here";
$matchLink = "\[link-ssec-(0?[1-9]|[1-9][0-9]|100)\]";
$matchItalic = "\[i](.+)\[\/i]";
$output = preg_replace_callback(
    "/(?|(?<link>$matchLink)|(?<italic>$matchItalic))/",
    function($m) {
        if(isset($m['link'])){
            $matchedLink = substr($m['link'][0], 1, -1);
            //error_log('m is: ' . $matchedLink);
            $linkIDExplode = explode("-",$matchedLink);
            $linkHTML = createSubSectionLink($linkIDExplode[2]);
            return $linkHTML;
        } else if(isset($m['italic'])){
            // TO DO
        }

    },
    $input);

If I remove the named capture groups, like so:

"/(?|(?:$matchLink)|(?:$matchItalic))/"

There's no warnings, and I get matches fine but can't target them conditionally in my function. I believe I'm following correct procedure for naming capture groups, but PHP is saying they're using the same subpattern number, which is where I'm lost as I'm not sure what's being numbered. I'm familiar with addressing subpatterns using $1, $2, etc. but don't see the relevancy here when used with named groups.


Goal

Incase I'm using completely the wrong technique, I should include my goal. I was originally using preg_replace_callback() to replace tagged strings that matched a pattern like so :

$output = preg_replace_callback(
    "/\[link-ssec-(0?[1-9]|[1-9][0-9]|100)\]/",
    function($m) {
        $matchedLink = substr($m[0], 1, -1);
        $linkIDExplode = explode("-",$matchedLink);
        $linkHTML = createSubSectionLink($linkIDExplode[2]);
        return $linkHTML;
    },
    $input);

The requirement has grown to needing to match multiple tags in the same paragraph (My original example included the next one [i]word[/i]. Rather than parsing the entire string from scratch for each pattern, I'm trying to look for all the patterns in a single sweep of the paragraph/string in the belief that it will be less taxing on the system. Researching it led me to believe that using named capture groups in a branch reset was the best means of being able to target matches with conditional statements. Perhaps I'm walking down the wrong trail with this one but would appreciate being directed to a better method.

Result Desired

$input = "A string that contains [link-ssec-34] and a [i]word[/i] here";
$output = "A string that contains <a href='linkfromdb.php'>Link from Database</a> and a <span class='italic'>word</span> here."

With the potential to add further patterns as needed in the format of square brackets encompassing a word or being self-contained.


Solution

  • To answer your question about the warning:

    PHP Warning: preg_replace_callback(): Compilation failed: different names for subpatterns of the same number are not allowed

    Your pattern defines named matchgroups. But your pattern is using alternations (|) as well, meaning a whole part of the pattern does not need to be matched as all.

    That means, that the named pattern link can appear with the match-number 1, but italic can also appear with match-number 1.

    Since there is an alternation BOTH the matches can only be the same "number", hence they are only allowed to have the same NAME:

    @(?|(?<first>one)|(?<first>two))@
    

    would be allowed.

    @(?|(?<first>one)|(?<second>two))@
    

    throws this warning.