Search code examples
phparrayspreg-replacehhvmpreg-replace-callback

How to convert preg_replace with pattern in array into preg_replace_callback?


I am trying to migrate my sites from PHP 5.4 to HHVM. I learned that preg_replace /e was E_DEPRECATED so I tried to convert all occurrences as possible. I got many parts corrected but fail to convert those with pattern in array. The following is one of the case:

if(strpos($msglower, '[/img]') !== FALSE) {
    $message = preg_replace(array(
        "/\[img\]\s*([^\[\<\r\n]+?)\s*\[\/img\]/ies",
        "/\[img=(\d{1,4})[x|\,](\d{1,4})\]\s*([^\[\<\r\n]+?)\s*\[\/img\]/ies"
    ), $allowimgcode ? array(
        "bbcodeurl('\\1', '<img src=\"%s\" border=\"0\" onclick=\"zoom(this, this.src)\" onload=\"attachimg(this, \'load\')\" alt=\"\" />')",
        "bbcodeurl('\\3', '<img width=\"\\1\" height=\"\\2\" src=\"%s\" border=\"0\" alt=\"\" />')"
    ) : array(
        "bbcodeurl('\\1', '<a href=\"%s\" target=\"_blank\">%s</a>')",
        "bbcodeurl('\\3', '<a href=\"%s\" target=\"_blank\">%s</a>')"
    ), $message);
}

I knew I should use anonymous function to convert but I am not sure where the function($matchese){return XXXXXXXXXXXX;} should be placed as there are more than one preg_replace /e within the pattern array. I tried to put at the beginning of REPLACEMENT string or before each array element. Both fail.I was stucked at this for two days now. Anyone can help me to solve this?

I tried to amend as your suggested by adding anonymous function blocks at each return array but it will give me empty content at that position:

if(strpos($msglower, '[/img]') !== FALSE) {
        $message = preg_replace_callback(array(
                "/\[img\]\s*([^\[\<\r\n]+?)\s*\[\/img\]/is",
                "/\[img=(\d{1,4})[x|\,](\d{1,4})\]\s*([^\[\<\r\n]+?)\s*\[\/img\]/is"
        ), $allowimgcode ? function($v) {return array(
                "bbcodeurl('$v[1]', '<img src=\"%s\" border=\"0\" onclick=\"zoom(this, this.src)\" onload=\"attachimg(this, \'load\')\" alt=\"\" />')",
                "bbcodeurl('$v[3]', '<img width=\"$v[1]\" height=\"$v[2]\" src=\"%s\" border=\"0\" alt=\"\" />')"
        );} : function($v) {return array(
                "bbcodeurl('$v[1]', '<a href=\"%s\" target=\"_blank\">%s</a>')",
                "bbcodeurl('$v[3]', '<a href=\"%s\" target=\"_blank\">%s</a>')"
        );}, $message);
}

This is one of my previous attempt actually. Seems it doesn't work.

As per reminded by axiac that the replacement part must be string and cannot be a array, I tried this and seems work:

if(strpos($msglower, '[/img]') !== FALSE) { $message = preg_replace_callback("/\[img\]\s*([^\[\<\r\n]+?)\s*\[\/img\]/is", $allowimgcode ? function($v) {return bbcodeurl($v[1], '<img src="%s" border="0" onclick="zoom(this, this.src)" onload="attachimg(this, \'load\')" alt="" />');} : function($v) {return bbcodeurl($v[1], '<a href="%s" target="_blank">%s</a>');}, $message); $message = preg_replace_callback("/\[img=(\d{1,4})[x|\,](\d{1,4})\]\s*([^\[\<\r\n]+?)\s*\[\/img\]/is", $allowimgcode ? function($v) {return bbcodeurl($v[3], "<img width=\"$v[1]\" height=\"$v[2]\" src=\"%s\" border=\"0\" alt=\"\" />");} : function($v) {return bbcodeurl($v[3], '<a href="%s" target="_blank">%s</a>');}, $message); }

Thanks a lot guys!


Solution

  • Calling preg_replace_callback() with an array of search strings is possible only when there is a single replacement for them (when the parameter $replacement of the call to preg_replace() you want to replace is a string, not an array). This is because the parameter provided to the callback does not contain any information about which of the search strings produced the match for which the callback is invoked.

    Because your replacements depend on the search string, I suggest you to do each replacement using a new call to preg_replace_callback(). This way you need to write individual functions for each set of search/replacement strings.

    The callback function receives as parameter the array of matches (the same array that is filled by preg_match() when received as its third argument). Use the use language construct (as explained in the third example on the documentation page about anonymous functions to let the callback know about the value of variable $allowimgcode:

    $message = preg_replace_callback(
        "/\[img\]\s*([^\[\<\r\n]+?)\s*\[\/img\]/is",
        function (array $matches) use ($allowimgcode) {
            if ($allowimgmode) {
                return bbcode($matches[1], '<img src="%s" border="0" onclick="zoom(this, this.src)" onload="attachimg(this, \'load\')" alt="" />');
            } else {
                return bbcode($matches[1], '<a href="%s" target="_blank">%s</a>');
            }
        }
        $message,
    );
    

    If there are many search/replacement pairs then you can organize the search together with the replacement functions into a list (and use separate callbacks for each scenario). The code is cleaner this way:

    // The search strings and their alternate replacements
    $replacements = array(
        array(
            // The search regex
            'search' => "/\[img\]\s*([^\[\<\r\n]+?)\s*\[\/img\]/is",
            // The callback when $allowimgcode is TRUE
            'images' => function (array $matches) {
                return bbcode($matches[1], '<img src="%s" border="0" onclick="zoom(this, this.src)" onload="attachimg(this, \'load\')" alt="" />');
            },
            // The callback when $allowimgcode is FALSE
            'noimgs' => function (array $matches) {
                return bbcode($matches[1], '<a href="%s" target="_blank">%s</a>');
            },
        ),
        array(
            'search' => "/\[img=(\d{1,4})[x|\,](\d{1,4})\]\s*([^\[\<\r\n]+?)\s*\[\/img\]/is",
            'images' => function (array $matches) {
                return bbcode($matches[3], '<img width="'.$matches[1].'" height="'.$matches[2].'" src="%s" border="0" alt="" />');
            },
            'noimgs' => function (array $matches) {
                return bbcode($matches[3], '<a href="%s" target="_blank">%s</a>');
            },
        ),
    );
    

    Then do the replacements one by one, in a loop:

    foreach ($replacements as $set) {
        $message = preg_replace_callback(
            $set['search'],
            // select the correct replacement callback depending on $allowimgcode
            $allowimgcode ? $set['images'] : $set['noimgs'], 
            $message
        );
    }