5

How can I keep track of the current match’s offset from the start of the string in the callback of preg_replace_callback?

For example, in this code, I’d like to point to the location of the match that throws the exception:

$substituted = preg_replace_callback('/{([a-z]+)}/', function ($match) use ($vars) {
    $name = $match[1];

    if (isset($vars[$name])) {
        return $vars[$name];
    }

    $offset = /* ? */;
    throw new Exception("undefined variable $name at byte $offset of template");
}, $template);
mickmackusa
  • 43,625
  • 12
  • 83
  • 136
Ry-
  • 218,210
  • 55
  • 464
  • 476

3 Answers3

7

As the marked answer is no longer available, here's what worked for me to get current index of replacement:

$index = 0;
preg_replace_callback($pattern, function($matches) use (&$index){
    $index++;
}, $content);

As you can see we have to maintain the index ourselves with the use of out-of-scope variable.

eithed
  • 3,933
  • 6
  • 40
  • 60
2

You can match first with preg_match_all & PREG_OFFSET_CAPTURE option and rebuild your string, instead of using the default preg_replace method.

Ry-
  • 218,210
  • 55
  • 464
  • 476
David Rodrigues
  • 12,041
  • 16
  • 62
  • 90
1

As of PHP 7.4.0, preg_replace_callback also accepts the PREG_OFFSET_CAPTURE flag, turning every match group into a [text, offset] pair:

$substituted = preg_replace_callback('/{([a-z]+)}/', function ($match) use ($vars) {
    $name = $match[1][0];

    if (isset($vars[$name])) {
        return $vars[$name];
    }

    $offset = $match[0][1];
    throw new Exception("undefined variable $name at byte $offset of template");
}, $template, flags: PREG_OFFSET_CAPTURE);
Ry-
  • 218,210
  • 55
  • 464
  • 476